2021-05-14 08:15:15 +00:00
|
|
|
import {
|
|
|
|
Button,
|
|
|
|
Dialog,
|
|
|
|
DialogActions,
|
|
|
|
DialogContent,
|
|
|
|
DialogTitle,
|
|
|
|
TextField,
|
|
|
|
Typography
|
|
|
|
} from "@material-ui/core";
|
2019-06-19 14:40:52 +00:00
|
|
|
import AutocompleteSelectMenu from "@saleor/components/AutocompleteSelectMenu";
|
|
|
|
import ConfirmButton, {
|
|
|
|
ConfirmButtonTransitionState
|
|
|
|
} from "@saleor/components/ConfirmButton";
|
|
|
|
import FormSpacer from "@saleor/components/FormSpacer";
|
2020-07-07 10:14:12 +00:00
|
|
|
import { MenuErrorFragment } from "@saleor/fragments/types/MenuErrorFragment";
|
2019-08-09 11:14:35 +00:00
|
|
|
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
|
2019-10-02 09:06:01 +00:00
|
|
|
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
2019-08-09 11:14:35 +00:00
|
|
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
2019-08-26 14:51:47 +00:00
|
|
|
import { buttonMessages, sectionNames } from "@saleor/intl";
|
2019-11-19 15:47:12 +00:00
|
|
|
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
|
2019-11-19 16:04:53 +00:00
|
|
|
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
|
2019-11-19 16:40:23 +00:00
|
|
|
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
|
2020-03-11 13:03:31 +00:00
|
|
|
import { getFieldError, getFormErrors } from "@saleor/utils/errors";
|
|
|
|
import getMenuErrorMessage from "@saleor/utils/errors/menu";
|
2020-05-14 09:30:32 +00:00
|
|
|
import { getMenuItemByValue, IMenu } from "@saleor/utils/menu";
|
|
|
|
import isUrl from "is-url";
|
|
|
|
import React from "react";
|
|
|
|
import { FormattedMessage, useIntl } from "react-intl";
|
2019-06-19 14:40:52 +00:00
|
|
|
|
|
|
|
export type MenuItemType = "category" | "collection" | "link" | "page";
|
|
|
|
export interface MenuItemData {
|
|
|
|
id: string;
|
|
|
|
type: MenuItemType;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface MenuItemDialogFormData extends MenuItemData {
|
|
|
|
name: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface MenuItemDialogProps {
|
|
|
|
confirmButtonState: ConfirmButtonTransitionState;
|
|
|
|
disabled: boolean;
|
2020-03-11 13:03:31 +00:00
|
|
|
errors: MenuErrorFragment[];
|
2019-06-19 14:40:52 +00:00
|
|
|
initial?: MenuItemDialogFormData;
|
|
|
|
initialDisplayValue?: string;
|
|
|
|
loading: boolean;
|
|
|
|
open: boolean;
|
2019-10-15 12:17:35 +00:00
|
|
|
collections: SearchCollections_search_edges_node[];
|
|
|
|
categories: SearchCategories_search_edges_node[];
|
|
|
|
pages: SearchPages_search_edges_node[];
|
2019-06-19 14:40:52 +00:00
|
|
|
onClose: () => void;
|
|
|
|
onSubmit: (data: MenuItemDialogFormData) => void;
|
|
|
|
onQueryChange: (query: string) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultInitial: MenuItemDialogFormData = {
|
|
|
|
id: "",
|
|
|
|
name: "",
|
|
|
|
type: "category"
|
|
|
|
};
|
|
|
|
|
|
|
|
function getMenuItemData(value: string): MenuItemData {
|
|
|
|
const [type, ...idParts] = value.split(":");
|
|
|
|
return {
|
|
|
|
id: idParts.join(":"),
|
|
|
|
type: type as MenuItemType
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDisplayValue(menu: IMenu, value: string): string {
|
|
|
|
const menuItemData = getMenuItemData(value);
|
|
|
|
if (menuItemData.type === "link") {
|
|
|
|
return menuItemData.id;
|
|
|
|
}
|
|
|
|
return getMenuItemByValue(menu, value).label.toString();
|
|
|
|
}
|
|
|
|
|
2019-11-07 11:34:54 +00:00
|
|
|
const MenuItemDialog: React.FC<MenuItemDialogProps> = ({
|
2019-06-19 14:40:52 +00:00
|
|
|
confirmButtonState,
|
|
|
|
disabled,
|
2019-08-09 11:14:35 +00:00
|
|
|
errors: apiErrors,
|
2019-06-19 14:40:52 +00:00
|
|
|
initial,
|
|
|
|
initialDisplayValue,
|
|
|
|
loading,
|
|
|
|
onClose,
|
|
|
|
onSubmit,
|
|
|
|
onQueryChange,
|
|
|
|
open,
|
|
|
|
categories,
|
|
|
|
collections,
|
|
|
|
pages
|
|
|
|
}) => {
|
2019-08-26 14:51:47 +00:00
|
|
|
const intl = useIntl();
|
2019-08-09 11:14:35 +00:00
|
|
|
const errors = useModalDialogErrors(apiErrors, open);
|
2019-06-19 14:40:52 +00:00
|
|
|
const [displayValue, setDisplayValue] = React.useState(
|
|
|
|
initialDisplayValue || ""
|
|
|
|
);
|
2019-08-09 11:14:35 +00:00
|
|
|
const [data, setData] = useStateFromProps<MenuItemDialogFormData>(
|
|
|
|
initial || defaultInitial
|
|
|
|
);
|
2019-06-19 14:40:52 +00:00
|
|
|
const [url, setUrl] = React.useState<string>(undefined);
|
|
|
|
|
2019-09-30 13:14:11 +00:00
|
|
|
// Reset input state after closing dialog
|
|
|
|
useModalDialogOpen(open, {
|
|
|
|
onClose: () => {
|
|
|
|
setData(initial || defaultInitial);
|
|
|
|
setDisplayValue(initialDisplayValue);
|
|
|
|
setUrl(undefined);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-06-19 14:40:52 +00:00
|
|
|
// Refresh initial display value if changed
|
|
|
|
React.useEffect(() => setDisplayValue(initialDisplayValue), [
|
|
|
|
initialDisplayValue
|
|
|
|
]);
|
|
|
|
|
2020-03-11 13:03:31 +00:00
|
|
|
const mutationErrors = errors.filter(err => err.field === null);
|
|
|
|
const formErrors = getFormErrors(["name"], errors);
|
2021-07-15 12:31:05 +00:00
|
|
|
const testIds = ["category", "collection", "page", "url"];
|
2020-03-11 13:03:31 +00:00
|
|
|
const idError = ["category", "collection", "page", "url"]
|
|
|
|
.map(field => getFieldError(errors, field))
|
|
|
|
.reduce((acc, err) => acc || err);
|
2019-08-09 11:14:35 +00:00
|
|
|
|
2019-06-19 14:40:52 +00:00
|
|
|
let options: IMenu = [];
|
|
|
|
|
|
|
|
if (categories.length > 0) {
|
|
|
|
options = [
|
|
|
|
...options,
|
|
|
|
{
|
|
|
|
children: categories.map(category => ({
|
|
|
|
children: [],
|
|
|
|
data: {},
|
|
|
|
label: category.name,
|
|
|
|
value: "category:" + category.id
|
|
|
|
})),
|
|
|
|
data: {},
|
2019-08-26 14:51:47 +00:00
|
|
|
label: intl.formatMessage(sectionNames.categories)
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (collections.length > 0) {
|
|
|
|
options = [
|
|
|
|
...options,
|
|
|
|
{
|
|
|
|
children: collections.map(collection => ({
|
|
|
|
children: [],
|
|
|
|
data: {},
|
|
|
|
label: collection.name,
|
|
|
|
value: "collection:" + collection.id
|
|
|
|
})),
|
|
|
|
data: {},
|
2019-08-26 14:51:47 +00:00
|
|
|
label: intl.formatMessage(sectionNames.collections)
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pages.length > 0) {
|
|
|
|
options = [
|
|
|
|
...options,
|
|
|
|
{
|
|
|
|
children: pages.map(page => ({
|
|
|
|
children: [],
|
|
|
|
data: {},
|
|
|
|
label: page.title,
|
|
|
|
value: "page:" + page.id
|
|
|
|
})),
|
|
|
|
data: {},
|
2019-08-26 14:51:47 +00:00
|
|
|
label: intl.formatMessage(sectionNames.pages)
|
2019-06-19 14:40:52 +00:00
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url) {
|
|
|
|
options = [
|
|
|
|
{
|
|
|
|
children: [],
|
|
|
|
data: {},
|
|
|
|
label: (
|
2019-08-26 14:51:47 +00:00
|
|
|
<FormattedMessage
|
|
|
|
defaultMessage="Link to: {url}"
|
|
|
|
description="add link to navigation"
|
|
|
|
id="menuItemDialogAddLink"
|
|
|
|
values={{
|
|
|
|
url: <strong>{url}</strong>
|
2019-06-19 14:40:52 +00:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
value: "link:" + url
|
|
|
|
}
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleQueryChange = (query: string) => {
|
|
|
|
if (isUrl(query)) {
|
|
|
|
setUrl(query);
|
|
|
|
} else if (isUrl("http://" + query)) {
|
|
|
|
setUrl("http://" + query);
|
|
|
|
} else if (url) {
|
|
|
|
setUrl(undefined);
|
|
|
|
}
|
|
|
|
onQueryChange(query);
|
|
|
|
};
|
|
|
|
|
2019-08-09 11:14:35 +00:00
|
|
|
const handleSelectChange = (event: React.ChangeEvent<any>) => {
|
|
|
|
const value = event.target.value;
|
|
|
|
const menuItemData = getMenuItemData(value);
|
|
|
|
|
|
|
|
setData(value => ({
|
|
|
|
...value,
|
|
|
|
...menuItemData
|
|
|
|
}));
|
|
|
|
setDisplayValue(getDisplayValue(options, value));
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleSubmit = () => onSubmit(data);
|
|
|
|
|
2019-06-19 14:40:52 +00:00
|
|
|
return (
|
|
|
|
<Dialog
|
|
|
|
onClose={onClose}
|
|
|
|
open={open}
|
|
|
|
maxWidth="sm"
|
|
|
|
fullWidth
|
|
|
|
PaperProps={{
|
|
|
|
style: { overflowY: "visible" }
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<DialogTitle>
|
2019-08-09 11:14:35 +00:00
|
|
|
{!!initial
|
2019-08-26 14:51:47 +00:00
|
|
|
? intl.formatMessage({
|
|
|
|
defaultMessage: "Edit Item",
|
|
|
|
description: "edit menu item, header",
|
|
|
|
id: "menuItemDialogEditItem"
|
2019-08-09 11:14:35 +00:00
|
|
|
})
|
2019-08-26 14:51:47 +00:00
|
|
|
: intl.formatMessage({
|
|
|
|
defaultMessage: "Add Item",
|
|
|
|
description: "create new menu item, header",
|
|
|
|
id: "menuItemDialogAddItem"
|
2019-08-09 11:14:35 +00:00
|
|
|
})}
|
2019-06-19 14:40:52 +00:00
|
|
|
</DialogTitle>
|
2019-08-09 11:14:35 +00:00
|
|
|
<DialogContent style={{ overflowY: "visible" }}>
|
|
|
|
<TextField
|
|
|
|
disabled={disabled}
|
2019-08-26 14:51:47 +00:00
|
|
|
label={intl.formatMessage({
|
|
|
|
defaultMessage: "Name",
|
|
|
|
description: "menu item name",
|
|
|
|
id: "menuItemDialogNameLabel"
|
|
|
|
})}
|
2019-08-09 11:14:35 +00:00
|
|
|
fullWidth
|
|
|
|
value={data.name}
|
|
|
|
onChange={event =>
|
|
|
|
setData(value => ({
|
|
|
|
...value,
|
|
|
|
name: event.target.value
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
name="name"
|
2020-03-11 13:03:31 +00:00
|
|
|
error={!!formErrors.name}
|
|
|
|
helperText={getMenuErrorMessage(formErrors.name, intl)}
|
2019-08-09 11:14:35 +00:00
|
|
|
/>
|
|
|
|
<FormSpacer />
|
|
|
|
<AutocompleteSelectMenu
|
|
|
|
disabled={disabled}
|
|
|
|
onChange={handleSelectChange}
|
|
|
|
name="id"
|
2019-08-26 14:51:47 +00:00
|
|
|
label={intl.formatMessage({
|
|
|
|
defaultMessage: "Link",
|
|
|
|
description: "label",
|
|
|
|
id: "menuItemDialogLinkLabel"
|
|
|
|
})}
|
2019-08-09 11:14:35 +00:00
|
|
|
displayValue={displayValue}
|
|
|
|
loading={loading}
|
|
|
|
options={options}
|
2021-07-15 12:31:05 +00:00
|
|
|
testIds={testIds}
|
2019-08-09 11:14:35 +00:00
|
|
|
error={!!idError}
|
2020-03-11 13:03:31 +00:00
|
|
|
helperText={getMenuErrorMessage(idError, intl)}
|
2019-08-26 14:51:47 +00:00
|
|
|
placeholder={intl.formatMessage({
|
|
|
|
defaultMessage: "Start typing to begin search...",
|
|
|
|
id: "menuItemDialogLinkPlaceholder"
|
|
|
|
})}
|
2019-08-09 11:14:35 +00:00
|
|
|
onInputChange={handleQueryChange}
|
|
|
|
/>
|
|
|
|
{mutationErrors.length > 0 && (
|
|
|
|
<>
|
|
|
|
<FormSpacer />
|
|
|
|
{mutationErrors.map(err => (
|
2020-03-11 13:03:31 +00:00
|
|
|
<Typography key={err.code} color="error">
|
|
|
|
{getMenuErrorMessage(err, intl)}
|
2019-08-09 11:14:35 +00:00
|
|
|
</Typography>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</DialogContent>
|
|
|
|
<DialogActions>
|
|
|
|
<Button onClick={onClose}>
|
2019-10-21 14:46:12 +00:00
|
|
|
<FormattedMessage {...buttonMessages.back} />
|
2019-08-09 11:14:35 +00:00
|
|
|
</Button>
|
|
|
|
<ConfirmButton
|
2021-07-15 12:31:05 +00:00
|
|
|
data-test="submit"
|
2019-08-09 11:14:35 +00:00
|
|
|
transitionState={confirmButtonState}
|
|
|
|
color="primary"
|
|
|
|
variant="contained"
|
|
|
|
onClick={handleSubmit}
|
|
|
|
>
|
2019-08-26 14:51:47 +00:00
|
|
|
<FormattedMessage {...buttonMessages.confirm} />
|
2019-08-09 11:14:35 +00:00
|
|
|
</ConfirmButton>
|
|
|
|
</DialogActions>
|
2019-06-19 14:40:52 +00:00
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
MenuItemDialog.displayName = "MenuItemDialog";
|
|
|
|
export default MenuItemDialog;
|