saleor-dashboard/src/navigation/components/MenuItemDialog/MenuItemDialog.tsx

313 lines
8.5 KiB
TypeScript
Raw Normal View History

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";
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";
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";
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();
}
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
}) => {
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);
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: {},
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: {},
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: {},
label: intl.formatMessage(sectionNames.pages)
2019-06-19 14:40:52 +00:00
}
];
}
if (url) {
options = [
{
children: [],
data: {},
label: (
<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
? intl.formatMessage({
defaultMessage: "Edit Item",
description: "edit menu item, header",
id: "menuItemDialogEditItem"
2019-08-09 11:14:35 +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}
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"
label={intl.formatMessage({
defaultMessage: "Link",
description: "label",
id: "menuItemDialogLinkLabel"
})}
2019-08-09 11:14:35 +00:00
displayValue={displayValue}
loading={loading}
options={options}
testIds={testIds}
2019-08-09 11:14:35 +00:00
error={!!idError}
2020-03-11 13:03:31 +00:00
helperText={getMenuErrorMessage(idError, intl)}
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
data-test="submit"
2019-08-09 11:14:35 +00:00
transitionState={confirmButtonState}
color="primary"
variant="contained"
onClick={handleSubmit}
>
<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;