Enable save button on page pages (#2325)
* Enable save button on page edit pages * Update e2e page create tests * Update page create tests snapshots * Update changelog with enable save button * Update messages of pages views * Update page details messages
This commit is contained in:
parent
aceae75ce7
commit
43fb52bc56
19 changed files with 469 additions and 176 deletions
|
@ -13,6 +13,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Handle form errors before product creation - #2299 by @orzechdev
|
||||
- Fix no product error on unconfirmed order lines - #2324 by @orzechdev
|
||||
- Enable save button on discount pages - #2319 by @orzechdev
|
||||
- Enable save button on page pages - #2325 by @orzechdev
|
||||
|
||||
## 3.4
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const PAGES_LIST = {
|
||||
createPageButton: '[data-test-id="create-page"]'
|
||||
createPageButton: '[data-test-id="create-page"]',
|
||||
dialogPageTypeInput: "[data-test-id='dialog-page-type']",
|
||||
};
|
||||
|
|
|
@ -11,6 +11,17 @@ export const attributesTypes = {
|
|||
BOOLEAN: addBooleanAttributeValue,
|
||||
NUMERIC: addNumericAttributeValue,
|
||||
};
|
||||
|
||||
export function fillUpPageTypeDialog({ pageTypeName }) {
|
||||
const organization = {};
|
||||
return cy
|
||||
.fillAutocompleteSelect(PAGES_LIST.dialogPageTypeInput, pageTypeName)
|
||||
.then(selected => {
|
||||
organization.pageType = selected;
|
||||
return organization;
|
||||
});
|
||||
}
|
||||
|
||||
export function createPage({
|
||||
pageName,
|
||||
pageTypeName,
|
||||
|
@ -52,6 +63,9 @@ function openCreatePageAndFillUpGeneralFields({
|
|||
}) {
|
||||
cy.visit(urlList.pages)
|
||||
.get(PAGES_LIST.createPageButton)
|
||||
.click();
|
||||
fillUpPageTypeDialog({ pageTypeName });
|
||||
cy.get(BUTTON_SELECTORS.submit)
|
||||
.click()
|
||||
.get(PAGE_DETAILS.nameInput)
|
||||
.type(pageName);
|
||||
|
|
|
@ -2059,6 +2059,10 @@
|
|||
"context": "invoice create date prefix",
|
||||
"string": "created"
|
||||
},
|
||||
"F0N1SC": {
|
||||
"context": "dialog header",
|
||||
"string": "Select a page type"
|
||||
},
|
||||
"F3Upht": {
|
||||
"string": "Product type deleted"
|
||||
},
|
||||
|
@ -2596,6 +2600,10 @@
|
|||
"context": "section header",
|
||||
"string": "You are about to install {name}"
|
||||
},
|
||||
"Id9vlh": {
|
||||
"context": "page seo options description",
|
||||
"string": "Add search engine title and description to make this page easier to find"
|
||||
},
|
||||
"IeoGgH": {
|
||||
"context": "variant created success message",
|
||||
"string": "Variant created"
|
||||
|
@ -4211,6 +4219,10 @@
|
|||
"context": "dialog content",
|
||||
"string": "Removed sale"
|
||||
},
|
||||
"V45+rx": {
|
||||
"context": "input label",
|
||||
"string": "Page type"
|
||||
},
|
||||
"V8FhTt": {
|
||||
"context": "collection label",
|
||||
"string": "Hidden"
|
||||
|
@ -5890,9 +5902,6 @@
|
|||
"context": "product availability",
|
||||
"string": "Hide in product listings"
|
||||
},
|
||||
"jZbT0O": {
|
||||
"string": "Add search engine title and description to make this page easier to find"
|
||||
},
|
||||
"jd/LWa": {
|
||||
"string": "Voucher applies to all countries"
|
||||
},
|
||||
|
|
|
@ -35,6 +35,7 @@ import { useIntl } from "react-intl";
|
|||
import PageInfo from "../PageInfo";
|
||||
import PageOrganizeContent from "../PageOrganizeContent";
|
||||
import PageForm, { PageData, PageUpdateHandlers } from "./form";
|
||||
import { messages } from "./messages";
|
||||
|
||||
export interface PageDetailsPageProps {
|
||||
loading: boolean;
|
||||
|
@ -68,7 +69,7 @@ export interface PageDetailsPageProps {
|
|||
|
||||
const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
||||
loading,
|
||||
errors,
|
||||
errors: apiErrors,
|
||||
page,
|
||||
pageTypes: pageTypeChoiceList,
|
||||
referencePages,
|
||||
|
@ -142,24 +143,21 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
|||
{({
|
||||
change,
|
||||
data,
|
||||
validationErrors,
|
||||
handlers,
|
||||
submit,
|
||||
isSaveDisabled,
|
||||
attributeRichTextGetters,
|
||||
}) => (
|
||||
}) => {
|
||||
const errors = [...apiErrors, ...validationErrors];
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Backlink href={pageListUrl()}>
|
||||
{intl.formatMessage(sectionNames.pages)}
|
||||
</Backlink>
|
||||
<PageHeader
|
||||
title={
|
||||
!pageExists
|
||||
? intl.formatMessage({
|
||||
id: "gr53VQ",
|
||||
defaultMessage: "Create Page",
|
||||
description: "page header",
|
||||
})
|
||||
: page?.title
|
||||
!pageExists ? intl.formatMessage(messages.title) : page?.title
|
||||
}
|
||||
/>
|
||||
<Grid>
|
||||
|
@ -182,11 +180,9 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
|||
slugPlaceholder={data.title}
|
||||
title={data.seoTitle}
|
||||
titlePlaceholder={data.title}
|
||||
helperText={intl.formatMessage({
|
||||
id: "jZbT0O",
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this page easier to find",
|
||||
})}
|
||||
helperText={intl.formatMessage(
|
||||
messages.seoOptionsDescription,
|
||||
)}
|
||||
/>
|
||||
<CardSpacer />
|
||||
{data.attributes.length > 0 && (
|
||||
|
@ -217,26 +213,14 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
|||
errors={errors}
|
||||
disabled={loading}
|
||||
messages={{
|
||||
hiddenLabel: intl.formatMessage({
|
||||
id: "/TK7QD",
|
||||
defaultMessage: "Hidden",
|
||||
description: "page label",
|
||||
}),
|
||||
hiddenLabel: intl.formatMessage(messages.hiddenLabel),
|
||||
hiddenSecondLabel: intl.formatMessage(
|
||||
{
|
||||
id: "GZgjK7",
|
||||
defaultMessage: "will be visible from {date}",
|
||||
description: "page",
|
||||
},
|
||||
messages.hiddenSecondLabel,
|
||||
{
|
||||
date: localizeDate(data.publicationDate),
|
||||
},
|
||||
),
|
||||
visibleLabel: intl.formatMessage({
|
||||
id: "X26jCC",
|
||||
defaultMessage: "Visible",
|
||||
description: "page label",
|
||||
}),
|
||||
visibleLabel: intl.formatMessage(messages.visibleLabel),
|
||||
}}
|
||||
onChange={change}
|
||||
/>
|
||||
|
@ -256,7 +240,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
|||
</div>
|
||||
</Grid>
|
||||
<Savebar
|
||||
disabled={isSaveDisabled}
|
||||
disabled={loading}
|
||||
state={saveButtonBarState}
|
||||
onCancel={() => navigate(pageListUrl())}
|
||||
onDelete={page === null ? undefined : onRemove}
|
||||
|
@ -278,12 +262,17 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
|
|||
loading={handlers.fetchMoreReferences?.loading}
|
||||
onClose={onCloseDialog}
|
||||
onSubmit={attributeValues =>
|
||||
handleAssignReferenceAttribute(attributeValues, data, handlers)
|
||||
handleAssignReferenceAttribute(
|
||||
attributeValues,
|
||||
data,
|
||||
handlers,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</PageForm>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
|||
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||
import {
|
||||
PageDetailsFragment,
|
||||
PageErrorWithAttributesFragment,
|
||||
SearchPagesQuery,
|
||||
SearchPageTypesQuery,
|
||||
SearchProductsQuery,
|
||||
|
@ -39,6 +40,7 @@ import {
|
|||
getAttributeInputFromPageType,
|
||||
} from "@saleor/pages/utils/data";
|
||||
import { createPageTypeSelectHandler } from "@saleor/pages/utils/handlers";
|
||||
import { validatePageCreateData } from "@saleor/pages/utils/validation";
|
||||
import { FetchMoreProps, RelayToFlat, ReorderEvent } from "@saleor/types";
|
||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
|
@ -47,7 +49,7 @@ import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTr
|
|||
import { RichTextContext } from "@saleor/utils/richText/context";
|
||||
import { useMultipleRichText } from "@saleor/utils/richText/useMultipleRichText";
|
||||
import useRichText from "@saleor/utils/richText/useRichText";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export interface PageFormData extends MetadataFormData {
|
||||
isPublished: boolean;
|
||||
|
@ -85,6 +87,7 @@ export interface UsePageUpdateFormOutput
|
|||
extends CommonUseFormResultWithHandlers<PageData, PageUpdateHandlers>,
|
||||
RichTextProps {
|
||||
valid: boolean;
|
||||
validationErrors: PageErrorWithAttributesFragment[];
|
||||
}
|
||||
|
||||
export type UsePageUpdateFormRenderProps = Omit<
|
||||
|
@ -142,6 +145,9 @@ function usePageForm(
|
|||
confirmLeave: true,
|
||||
},
|
||||
);
|
||||
const [validationErrors, setValidationErrors] = useState<
|
||||
PageErrorWithAttributesFragment[]
|
||||
>([]);
|
||||
|
||||
const attributes = useFormset(
|
||||
pageExists
|
||||
|
@ -160,7 +166,11 @@ function usePageForm(
|
|||
});
|
||||
const attributesWithNewFileValue = useFormset<null, File>([]);
|
||||
|
||||
const { setExitDialogSubmitRef, setIsSubmitDisabled } = useExitFormDialog({
|
||||
const {
|
||||
setExitDialogSubmitRef,
|
||||
setIsSubmitDisabled,
|
||||
setIsDirty,
|
||||
} = useExitFormDialog({
|
||||
formId,
|
||||
});
|
||||
|
||||
|
@ -247,7 +257,15 @@ function usePageForm(
|
|||
});
|
||||
|
||||
const handleSubmit = async (data: PageData) => {
|
||||
const errors = await onSubmit(data);
|
||||
let errors = validatePageCreateData(data);
|
||||
|
||||
setValidationErrors(errors);
|
||||
|
||||
if (errors.length) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
errors = await onSubmit(data);
|
||||
|
||||
if (!errors?.length && pageExists) {
|
||||
attributesWithNewFileValue.set([]);
|
||||
|
@ -261,18 +279,34 @@ function usePageForm(
|
|||
onSubmit: handleSubmit,
|
||||
});
|
||||
|
||||
const submit = async () => handleFormSubmit(await getSubmitData());
|
||||
const submit = async () => {
|
||||
const errors = await handleFormSubmit(await getSubmitData());
|
||||
|
||||
if (errors.length) {
|
||||
setIsSubmitDisabled(isSaveDisabled);
|
||||
setIsDirty(true);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||
|
||||
const valid = pageExists || !!opts.selectedPageType;
|
||||
|
||||
const isSaveDisabled = disabled || !valid;
|
||||
|
||||
useEffect(() => {
|
||||
setIsSubmitDisabled(isSaveDisabled);
|
||||
if (!pageExists) {
|
||||
setIsDirty(true);
|
||||
}
|
||||
}, [isSaveDisabled]);
|
||||
|
||||
return {
|
||||
change: handleChange,
|
||||
data,
|
||||
validationErrors,
|
||||
valid,
|
||||
handlers: {
|
||||
changeMetadata,
|
||||
|
|
30
src/pages/components/PageDetailsPage/messages.ts
Normal file
30
src/pages/components/PageDetailsPage/messages.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
title: {
|
||||
id: "gr53VQ",
|
||||
defaultMessage: "Create Page",
|
||||
description: "page header",
|
||||
},
|
||||
seoOptionsDescription: {
|
||||
id: "Id9vlh",
|
||||
defaultMessage:
|
||||
"Add search engine title and description to make this page easier to find",
|
||||
description: "page seo options description",
|
||||
},
|
||||
hiddenLabel: {
|
||||
id: "/TK7QD",
|
||||
defaultMessage: "Hidden",
|
||||
description: "page label",
|
||||
},
|
||||
hiddenSecondLabel: {
|
||||
id: "GZgjK7",
|
||||
defaultMessage: "will be visible from {date}",
|
||||
description: "page",
|
||||
},
|
||||
visibleLabel: {
|
||||
id: "X26jCC",
|
||||
defaultMessage: "Visible",
|
||||
description: "page label",
|
||||
},
|
||||
});
|
|
@ -5,7 +5,6 @@ import PageHeader from "@saleor/components/PageHeader";
|
|||
import { PageFragment } from "@saleor/graphql";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import {
|
||||
pageCreateUrl,
|
||||
PageListUrlDialog,
|
||||
PageListUrlQueryParams,
|
||||
PageListUrlSortField,
|
||||
|
@ -28,11 +27,13 @@ export interface PageListPageProps
|
|||
pages: PageFragment[];
|
||||
params: PageListUrlQueryParams;
|
||||
actionDialogOpts: PageListActionDialogOpts;
|
||||
onAdd: () => void;
|
||||
}
|
||||
|
||||
const PageListPage: React.FC<PageListPageProps> = ({
|
||||
params,
|
||||
actionDialogOpts,
|
||||
onAdd,
|
||||
...listProps
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
@ -40,11 +41,7 @@ const PageListPage: React.FC<PageListPageProps> = ({
|
|||
return (
|
||||
<Container>
|
||||
<PageHeader title={intl.formatMessage(sectionNames.pages)}>
|
||||
<Button
|
||||
href={pageCreateUrl()}
|
||||
variant="primary"
|
||||
data-test-id="create-page"
|
||||
>
|
||||
<Button onClick={onAdd} variant="primary" data-test-id="create-page">
|
||||
<FormattedMessage
|
||||
id="AHRDWt"
|
||||
defaultMessage="Create page"
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import Decorator from "@saleor/storybook/Decorator";
|
||||
import { mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import { pageTypesList } from "../../fixtures";
|
||||
import PageTypePickerDialog, {
|
||||
PageTypePickerDialogProps,
|
||||
} from "./PageTypePickerDialog";
|
||||
|
||||
const pageTypes = mapNodeToChoice(pageTypesList);
|
||||
|
||||
const props: PageTypePickerDialogProps = {
|
||||
pageTypes,
|
||||
confirmButtonState: "default",
|
||||
fetchPageTypes: () => undefined,
|
||||
fetchMorePageTypes: {
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
onFetchMore: () => undefined,
|
||||
},
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true,
|
||||
};
|
||||
|
||||
storiesOf("Views / Pages / Page type dialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <PageTypePickerDialog {...props} />);
|
|
@ -0,0 +1,70 @@
|
|||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import SingleAutocompleteSelectField, {
|
||||
SingleAutocompleteChoiceType,
|
||||
} from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { messages } from "./messages";
|
||||
|
||||
export interface PageTypePickerDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
pageTypes?: SingleAutocompleteChoiceType[];
|
||||
fetchPageTypes: (data: string) => void;
|
||||
fetchMorePageTypes: FetchMoreProps;
|
||||
onClose: () => void;
|
||||
onConfirm: (choice: string) => void;
|
||||
}
|
||||
|
||||
const PageTypePickerDialog: React.FC<PageTypePickerDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
pageTypes,
|
||||
fetchPageTypes,
|
||||
fetchMorePageTypes,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [choice, setChoice] = useStateFromProps("");
|
||||
const pageTypeDisplayValue = pageTypes.find(
|
||||
pageType => pageType.value === choice,
|
||||
)?.label;
|
||||
|
||||
useModalDialogOpen(open, {
|
||||
onClose: () => {
|
||||
setChoice("");
|
||||
fetchPageTypes("");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={() => onConfirm(choice)}
|
||||
title={intl.formatMessage(messages.selectPageType)}
|
||||
disabled={!choice}
|
||||
>
|
||||
<SingleAutocompleteSelectField
|
||||
displayValue={pageTypeDisplayValue}
|
||||
name="pageType"
|
||||
label={intl.formatMessage(messages.pageType)}
|
||||
choices={pageTypes}
|
||||
value={choice}
|
||||
onChange={e => setChoice(e.target.value)}
|
||||
fetchChoices={fetchPageTypes}
|
||||
data-test-id="dialog-page-type"
|
||||
{...fetchMorePageTypes}
|
||||
/>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
PageTypePickerDialog.displayName = "PageTypePickerDialog";
|
||||
export default PageTypePickerDialog;
|
2
src/pages/components/PageTypePickerDialog/index.ts
Normal file
2
src/pages/components/PageTypePickerDialog/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./PageTypePickerDialog";
|
||||
export { default } from "./PageTypePickerDialog";
|
14
src/pages/components/PageTypePickerDialog/messages.ts
Normal file
14
src/pages/components/PageTypePickerDialog/messages.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const messages = defineMessages({
|
||||
pageType: {
|
||||
id: "V45+rx",
|
||||
defaultMessage: "Page type",
|
||||
description: "input label",
|
||||
},
|
||||
selectPageType: {
|
||||
id: "F0N1SC",
|
||||
defaultMessage: "Select a page type",
|
||||
description: "dialog header",
|
||||
},
|
||||
});
|
|
@ -3,6 +3,7 @@ import {
|
|||
PageDetailsFragment,
|
||||
PageFragment,
|
||||
} from "@saleor/graphql";
|
||||
import { PageType } from "@saleor/sdk/dist/apollo/types";
|
||||
|
||||
import * as richTextEditorFixtures from "../components/RichTextEditor/fixtures.json";
|
||||
|
||||
|
@ -440,3 +441,18 @@ export const page: PageDetailsFragment = {
|
|||
slug: "about",
|
||||
title: "About",
|
||||
};
|
||||
|
||||
export const pageTypesList: Array<Pick<PageType, "id" | "name">> = [
|
||||
{
|
||||
id: "1111ZHVjdFR5cGU6Nw==",
|
||||
name: "General",
|
||||
},
|
||||
{
|
||||
id: "2222ZHVjdFR5cGU6Nw==",
|
||||
name: "Subpages",
|
||||
},
|
||||
{
|
||||
id: "3333ZHVjdFR5cGU6Nw==",
|
||||
name: "Blog",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
|||
import { WindowTitle } from "../components/WindowTitle";
|
||||
import {
|
||||
pageCreatePath,
|
||||
PageCreateUrlQueryParams,
|
||||
pageListPath,
|
||||
PageListUrlQueryParams,
|
||||
PageListUrlSortField,
|
||||
|
@ -30,7 +31,7 @@ const PageList: React.FC<RouteComponentProps<{}>> = ({ location }) => {
|
|||
|
||||
const PageCreate: React.FC<RouteComponentProps<any>> = ({ match }) => {
|
||||
const qs = parseQs(location.search.substr(1));
|
||||
const params: PageUrlQueryParams = qs;
|
||||
const params: PageCreateUrlQueryParams = qs;
|
||||
|
||||
return (
|
||||
<PageCreateComponent
|
||||
|
|
|
@ -20,6 +20,7 @@ export type PageListUrlDialog =
|
|||
| "publish"
|
||||
| "unpublish"
|
||||
| "remove"
|
||||
| "create-page"
|
||||
| TabActionDialog;
|
||||
export enum PageListUrlSortField {
|
||||
title = "title",
|
||||
|
@ -49,10 +50,16 @@ export const pageListUrl = (params?: PageListUrlQueryParams) =>
|
|||
|
||||
export const pagePath = (id: string) => urlJoin(pagesSection, id);
|
||||
export type PageUrlDialog = "remove" | "assign-attribute-value";
|
||||
export interface PageCreateUrlPageType {
|
||||
"page-type-id"?: string;
|
||||
}
|
||||
export type PageUrlQueryParams = Dialog<PageUrlDialog> & SingleAction;
|
||||
export type PageCreateUrlQueryParams = Dialog<PageUrlDialog> &
|
||||
SingleAction &
|
||||
PageCreateUrlPageType;
|
||||
export const pageUrl = (id: string, params?: PageUrlQueryParams) =>
|
||||
pagePath(encodeURIComponent(id)) + "?" + stringifyQs(params);
|
||||
|
||||
export const pageCreatePath = urlJoin(pagesSection, "add");
|
||||
export const pageCreateUrl = (params?: PageUrlQueryParams) =>
|
||||
export const pageCreateUrl = (params?: PageCreateUrlQueryParams) =>
|
||||
pageCreatePath + "?" + stringifyQs(params);
|
||||
|
|
30
src/pages/utils/validation.ts
Normal file
30
src/pages/utils/validation.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import {
|
||||
PageErrorCode,
|
||||
PageErrorWithAttributesFragment,
|
||||
} from "@saleor/graphql";
|
||||
|
||||
import { PageData } from "../components/PageDetailsPage/form";
|
||||
|
||||
const createEmptyRequiredError = (
|
||||
field: string,
|
||||
): PageErrorWithAttributesFragment => ({
|
||||
__typename: "PageError",
|
||||
code: PageErrorCode.REQUIRED,
|
||||
field,
|
||||
message: null,
|
||||
attributes: [],
|
||||
});
|
||||
|
||||
export const validatePageCreateData = (data: PageData) => {
|
||||
let errors: PageErrorWithAttributesFragment[] = [];
|
||||
|
||||
if (!data.pageType) {
|
||||
errors = [...errors, createEmptyRequiredError("pageType")];
|
||||
}
|
||||
|
||||
if (!data.title) {
|
||||
errors = [...errors, createEmptyRequiredError("title")];
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
|
@ -10,6 +10,7 @@ import {
|
|||
VALUES_PAGINATE_BY,
|
||||
} from "@saleor/config";
|
||||
import {
|
||||
PageErrorWithAttributesFragment,
|
||||
useFileUploadMutation,
|
||||
usePageCreateMutation,
|
||||
usePageTypeQuery,
|
||||
|
@ -31,11 +32,11 @@ import { useIntl } from "react-intl";
|
|||
|
||||
import PageDetailsPage from "../components/PageDetailsPage";
|
||||
import { PageSubmitData } from "../components/PageDetailsPage/form";
|
||||
import { pageCreateUrl, pageUrl, PageUrlQueryParams } from "../urls";
|
||||
import { pageCreateUrl, PageCreateUrlQueryParams, pageUrl } from "../urls";
|
||||
|
||||
export interface PageCreateProps {
|
||||
id: string;
|
||||
params: PageUrlQueryParams;
|
||||
params: PageCreateUrlQueryParams;
|
||||
}
|
||||
|
||||
export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
||||
|
@ -45,7 +46,15 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
|||
const [updateMetadata] = useUpdateMetadataMutation({});
|
||||
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
|
||||
|
||||
const [selectedPageTypeId, setSelectedPageTypeId] = React.useState<string>();
|
||||
const selectedPageTypeId = params["page-type-id"];
|
||||
|
||||
const handleSelectPageTypeId = (pageTypeId: string) =>
|
||||
navigate(
|
||||
pageCreateUrl({
|
||||
...params,
|
||||
"page-type-id": pageTypeId,
|
||||
}),
|
||||
);
|
||||
|
||||
const {
|
||||
loadMore: loadMorePageTypes,
|
||||
|
@ -178,6 +187,10 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
|||
onFetchMore: loadMoreAttributeValues,
|
||||
};
|
||||
|
||||
const errors = getMutationErrors(
|
||||
pageCreateOpts,
|
||||
) as PageErrorWithAttributesFragment[];
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle
|
||||
|
@ -189,7 +202,7 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
|||
/>
|
||||
<PageDetailsPage
|
||||
loading={pageCreateOpts.loading || uploadFileOpts.loading}
|
||||
errors={pageCreateOpts.data?.pageCreate.errors || []}
|
||||
errors={errors}
|
||||
saveButtonBarState={pageCreateOpts.status}
|
||||
page={null}
|
||||
attributeValues={attributeValues}
|
||||
|
@ -214,7 +227,7 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
|||
fetchMoreAttributeValues={fetchMoreAttributeValues}
|
||||
onCloseDialog={() => navigate(pageCreateUrl())}
|
||||
selectedPageType={selectedPageType?.pageType}
|
||||
onSelectPageType={id => setSelectedPageTypeId(id)}
|
||||
onSelectPageType={handleSelectPageTypeId}
|
||||
onAttributeSelectBlur={searchAttributeReset}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { DialogContentText } from "@material-ui/core";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { Button } from "@saleor/components/Button";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import {
|
||||
usePageBulkPublishMutation,
|
||||
usePageBulkRemoveMutation,
|
||||
|
@ -17,16 +18,19 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { DeleteIcon, IconButton } from "@saleor/macaw-ui";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import PageTypePickerDialog from "@saleor/pages/components/PageTypePickerDialog";
|
||||
import usePageTypeSearch from "@saleor/searches/usePageTypeSearch";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { mapEdgesToItems } from "@saleor/utils/maps";
|
||||
import { mapEdgesToItems, mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import PageListPage from "../../components/PageListPage/PageListPage";
|
||||
import {
|
||||
pageCreateUrl,
|
||||
pageListUrl,
|
||||
PageListUrlDialog,
|
||||
PageListUrlQueryParams,
|
||||
|
@ -114,6 +118,20 @@ export const PageList: React.FC<PageListProps> = ({ params }) => {
|
|||
|
||||
const handleSort = createSortHandler(navigate, pageListUrl, params);
|
||||
|
||||
const {
|
||||
loadMore: loadMoreDialogPageTypes,
|
||||
search: searchDialogPageTypes,
|
||||
result: searchDialogPageTypesOpts,
|
||||
} = usePageTypeSearch({
|
||||
variables: DEFAULT_INITIAL_SEARCH_DATA,
|
||||
});
|
||||
|
||||
const fetchMoreDialogPageTypes = {
|
||||
hasMore: searchDialogPageTypesOpts.data?.search?.pageInfo?.hasNextPage,
|
||||
loading: searchDialogPageTypesOpts.loading,
|
||||
onFetchMore: loadMoreDialogPageTypes,
|
||||
};
|
||||
|
||||
return (
|
||||
<PaginatorContext.Provider value={paginationValues}>
|
||||
<PageListPage
|
||||
|
@ -121,6 +139,7 @@ export const PageList: React.FC<PageListProps> = ({ params }) => {
|
|||
settings={settings}
|
||||
pages={mapEdgesToItems(data?.pages)}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
onAdd={() => openModal("create-page")}
|
||||
onSort={handleSort}
|
||||
actionDialogOpts={{
|
||||
open: openModal,
|
||||
|
@ -262,6 +281,23 @@ export const PageList: React.FC<PageListProps> = ({ params }) => {
|
|||
}}
|
||||
/>
|
||||
</ActionDialog>
|
||||
<PageTypePickerDialog
|
||||
confirmButtonState="success"
|
||||
open={params.action === "create-page"}
|
||||
pageTypes={mapNodeToChoice(
|
||||
mapEdgesToItems(searchDialogPageTypesOpts?.data?.search),
|
||||
)}
|
||||
fetchPageTypes={searchDialogPageTypes}
|
||||
fetchMorePageTypes={fetchMoreDialogPageTypes}
|
||||
onClose={closeModal}
|
||||
onConfirm={pageTypeId =>
|
||||
navigate(
|
||||
pageCreateUrl({
|
||||
"page-type-id": pageTypeId,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</PaginatorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -158149,20 +158149,18 @@ exports[`Storyshots Views / Pages / Page list default 1`] = `
|
|||
<div
|
||||
class="PageHeader-root-id"
|
||||
>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
<button
|
||||
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||
data-test-id="create-page"
|
||||
href="/pages/add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label-id"
|
||||
>
|
||||
Create page
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -158741,20 +158739,18 @@ exports[`Storyshots Views / Pages / Page list loading 1`] = `
|
|||
<div
|
||||
class="PageHeader-root-id"
|
||||
>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
<button
|
||||
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||
data-test-id="create-page"
|
||||
href="/pages/add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label-id"
|
||||
>
|
||||
Create page
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159136,20 +159132,18 @@ exports[`Storyshots Views / Pages / Page list no data 1`] = `
|
|||
<div
|
||||
class="PageHeader-root-id"
|
||||
>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
<button
|
||||
class="MuiButtonBase-root-id MuiButton-root-id MuiButton-contained-id MuiButton-containedPrimary-id"
|
||||
data-test-id="create-page"
|
||||
href="/pages/add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label-id"
|
||||
>
|
||||
Create page
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159402,6 +159396,12 @@ exports[`Storyshots Views / Pages / Page list no data 1`] = `
|
|||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Views / Pages / Page type dialog default 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Views / Permission Groups / Permission Group Create default 1`] = `
|
||||
<div
|
||||
style="padding:24px"
|
||||
|
|
Loading…
Reference in a new issue