Add metadata (#670)
* Add metadata editor component * Add tests * Fix plurals * Use pascal case in selectors * Update product metadata * Add metadata handler decorator * Update snapshots * wip * Remove operation provider component * Add metadata to collections * Add metadata editor to variant * Add metadata editor to categories * Add metadata to product types * Simplify code * Add metadata to attributes * Drop maybe * Rename Metadata to MetadataFragment * Update changelog and snapshots
This commit is contained in:
parent
265c8bc0ca
commit
7770ae34df
96 changed files with 12334 additions and 2002 deletions
|
@ -29,6 +29,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Fix navigator button rendering on safari browser - #656 by @dominik-zeglen
|
- Fix navigator button rendering on safari browser - #656 by @dominik-zeglen
|
||||||
- Use hooks instead of containers with render props in product mutations - #667 by @dominik-zeglen
|
- Use hooks instead of containers with render props in product mutations - #667 by @dominik-zeglen
|
||||||
- Add apps and permission groups to navigator - #678 by @dominik-zeglen
|
- Add apps and permission groups to navigator - #678 by @dominik-zeglen
|
||||||
|
- Add metadata - #670 by @dominik-zeglen
|
||||||
|
|
||||||
## 2.10.1
|
## 2.10.1
|
||||||
|
|
||||||
|
|
7
assets/images/empty-metadata.svg
Normal file
7
assets/images/empty-metadata.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="91" height="88" viewBox="0 0 91 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1 68.4054L43.7676 47.1814V1L1 22.224V68.4054Z" fill="#F1F6F6" stroke="#28234A" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M60.038 41.9077L87.9299 28.1653V10.2969L60.038 24.0393V41.9077Z" fill="#F1F6F6" stroke="#28234A" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M62.8279 86.998L90.2549 73.1248V45.625L62.8279 59.4981V86.998Z" fill="#F1F6F6" stroke="#28234A" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M46.5574 55.3909L60.0385 48.8828" stroke="#06847B" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M53.5302 43.7656V60.9656" stroke="#06847B" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 909 B |
|
@ -1549,6 +1549,46 @@
|
||||||
"context": "button",
|
"context": "button",
|
||||||
"string": "Add"
|
"string": "Add"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_1148029984": {
|
||||||
|
"context": "metadata field value, header",
|
||||||
|
"string": "Value"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_1509432322": {
|
||||||
|
"context": "header",
|
||||||
|
"string": "Metadata"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_1535223586": {
|
||||||
|
"context": "header",
|
||||||
|
"string": "Private Metadata"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_2024779015": {
|
||||||
|
"context": "empty metadata text",
|
||||||
|
"string": "There is no private metadata created for this element."
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_2087058956": {
|
||||||
|
"context": "empty metadata text",
|
||||||
|
"string": "There is no metadata created for this element."
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_2537046678": {
|
||||||
|
"context": "metadata field name, header",
|
||||||
|
"string": "Field"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_3181473584": {
|
||||||
|
"context": "add metadata field,button",
|
||||||
|
"string": "Add Field"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_4190792473": {
|
||||||
|
"context": "table action",
|
||||||
|
"string": "Actions"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_549104951": {
|
||||||
|
"context": "number of metadata fields in model",
|
||||||
|
"string": "{number,plural,one{{number} Field} other{{number} Fields}}"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Metadata_dot_553451245": {
|
||||||
|
"context": "empty metadata text",
|
||||||
|
"string": "Use the button below to add new metadata field"
|
||||||
|
},
|
||||||
"src_dot_components_dot_MoneyRange_dot_12301532": {
|
"src_dot_components_dot_MoneyRange_dot_12301532": {
|
||||||
"context": "money",
|
"context": "money",
|
||||||
"string": "to {money}"
|
"string": "to {money}"
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import {
|
import {
|
||||||
|
@ -15,6 +17,8 @@ import { sectionNames } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { ReorderAction } from "@saleor/types";
|
import { ReorderAction } from "@saleor/types";
|
||||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
|
@ -38,7 +42,7 @@ export interface AttributePageProps {
|
||||||
onValueUpdate: (id: string) => void;
|
onValueUpdate: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributePageFormData {
|
export interface AttributePageFormData extends MetadataFormData {
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
inputType: AttributeInputTypeEnum;
|
inputType: AttributeInputTypeEnum;
|
||||||
|
@ -65,6 +69,12 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
onValueUpdate
|
onValueUpdate
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const initialForm: AttributePageFormData =
|
const initialForm: AttributePageFormData =
|
||||||
attribute === null
|
attribute === null
|
||||||
? {
|
? {
|
||||||
|
@ -72,7 +82,9 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
filterableInDashboard: true,
|
filterableInDashboard: true,
|
||||||
filterableInStorefront: true,
|
filterableInStorefront: true,
|
||||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
metadata: undefined,
|
||||||
name: "",
|
name: "",
|
||||||
|
privateMetadata: undefined,
|
||||||
slug: "",
|
slug: "",
|
||||||
storefrontSearchPosition: "",
|
storefrontSearchPosition: "",
|
||||||
valueRequired: true,
|
valueRequired: true,
|
||||||
|
@ -92,7 +104,11 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
() => attribute.inputType,
|
() => attribute.inputType,
|
||||||
AttributeInputTypeEnum.DROPDOWN
|
AttributeInputTypeEnum.DROPDOWN
|
||||||
),
|
),
|
||||||
|
metadata: attribute?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: maybe(() => attribute.name, ""),
|
name: maybe(() => attribute.name, ""),
|
||||||
|
privateMetadata: attribute?.privateMetadata?.map(
|
||||||
|
mapMetadataItemToInput
|
||||||
|
),
|
||||||
slug: maybe(() => attribute.slug, ""),
|
slug: maybe(() => attribute.slug, ""),
|
||||||
storefrontSearchPosition: maybe(
|
storefrontSearchPosition: maybe(
|
||||||
() => attribute.storefrontSearchPosition.toString(),
|
() => attribute.storefrontSearchPosition.toString(),
|
||||||
|
@ -102,66 +118,84 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
visibleInStorefront: maybe(() => attribute.visibleInStorefront, true)
|
visibleInStorefront: maybe(() => attribute.visibleInStorefront, true)
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (data: AttributePageFormData) =>
|
const handleSubmit = (data: AttributePageFormData) => {
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
...data,
|
...data,
|
||||||
|
metadata,
|
||||||
|
privateMetadata,
|
||||||
slug: data.slug || slugify(data.name).toLowerCase()
|
slug: data.slug || slugify(data.name).toLowerCase()
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={handleSubmit}>
|
<Form initial={initialForm} onSubmit={handleSubmit}>
|
||||||
{({ change, data, submit }) => (
|
{({ change, data, submit }) => {
|
||||||
<Container>
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
<AppHeader onBack={onBack}>
|
|
||||||
{intl.formatMessage(sectionNames.attributes)}
|
return (
|
||||||
</AppHeader>
|
<Container>
|
||||||
<PageHeader
|
<AppHeader onBack={onBack}>
|
||||||
title={
|
{intl.formatMessage(sectionNames.attributes)}
|
||||||
attribute === null
|
</AppHeader>
|
||||||
? intl.formatMessage({
|
<PageHeader
|
||||||
defaultMessage: "Create New Attribute",
|
title={
|
||||||
description: "page title"
|
attribute === null
|
||||||
})
|
? intl.formatMessage({
|
||||||
: maybe(() => attribute.name)
|
defaultMessage: "Create New Attribute",
|
||||||
}
|
description: "page title"
|
||||||
/>
|
})
|
||||||
<Grid>
|
: maybe(() => attribute.name)
|
||||||
<div>
|
}
|
||||||
<AttributeDetails
|
/>
|
||||||
canChangeType={attribute === null}
|
<Grid>
|
||||||
data={data}
|
<div>
|
||||||
disabled={disabled}
|
<AttributeDetails
|
||||||
errors={errors}
|
canChangeType={attribute === null}
|
||||||
onChange={change}
|
data={data}
|
||||||
/>
|
disabled={disabled}
|
||||||
<CardSpacer />
|
errors={errors}
|
||||||
<AttributeValues
|
onChange={change}
|
||||||
disabled={disabled}
|
/>
|
||||||
values={values}
|
<CardSpacer />
|
||||||
onValueAdd={onValueAdd}
|
<AttributeValues
|
||||||
onValueDelete={onValueDelete}
|
disabled={disabled}
|
||||||
onValueReorder={onValueReorder}
|
values={values}
|
||||||
onValueUpdate={onValueUpdate}
|
onValueAdd={onValueAdd}
|
||||||
/>
|
onValueDelete={onValueDelete}
|
||||||
</div>
|
onValueReorder={onValueReorder}
|
||||||
<div>
|
onValueUpdate={onValueUpdate}
|
||||||
<AttributeProperties
|
/>
|
||||||
data={data}
|
{!!attribute && (
|
||||||
errors={errors}
|
<>
|
||||||
disabled={disabled}
|
<CardSpacer />
|
||||||
onChange={change}
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
/>
|
</>
|
||||||
</div>
|
)}
|
||||||
</Grid>
|
</div>
|
||||||
<SaveButtonBar
|
<div>
|
||||||
disabled={disabled}
|
<AttributeProperties
|
||||||
state={saveButtonBarState}
|
data={data}
|
||||||
onCancel={onBack}
|
errors={errors}
|
||||||
onSave={submit}
|
disabled={disabled}
|
||||||
onDelete={attribute === null ? undefined : onDelete}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</div>
|
||||||
)}
|
</Grid>
|
||||||
|
<SaveButtonBar
|
||||||
|
disabled={disabled}
|
||||||
|
state={saveButtonBarState}
|
||||||
|
onCancel={onBack}
|
||||||
|
onSave={submit}
|
||||||
|
onDelete={attribute === null ? undefined : onDelete}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,15 @@ export const attribute: AttributeDetailsFragment = {
|
||||||
filterableInStorefront: true,
|
filterableInStorefront: true,
|
||||||
id: "UHJvZHVjdEF0dHJpYnV0ZTo5",
|
id: "UHJvZHVjdEF0dHJpYnV0ZTo5",
|
||||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "Author",
|
name: "Author",
|
||||||
|
privateMetadata: [],
|
||||||
slug: "author",
|
slug: "author",
|
||||||
storefrontSearchPosition: 2,
|
storefrontSearchPosition: 2,
|
||||||
valueRequired: true,
|
valueRequired: true,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { attributeDetailsFragment } from "@saleor/fragments/attributes";
|
import { attributeDetailsFragment } from "@saleor/fragments/attributes";
|
||||||
import { productErrorFragment } from "@saleor/fragments/errors";
|
import { productErrorFragment } from "@saleor/fragments/errors";
|
||||||
import { TypedMutation } from "@saleor/mutations";
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -46,7 +46,7 @@ const attributeBulkDelete = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeBulkDeleteMutation = TypedMutation<
|
export const useAttributeBulkDeleteMutation = makeMutation<
|
||||||
AttributeBulkDelete,
|
AttributeBulkDelete,
|
||||||
AttributeBulkDeleteVariables
|
AttributeBulkDeleteVariables
|
||||||
>(attributeBulkDelete);
|
>(attributeBulkDelete);
|
||||||
|
@ -61,7 +61,7 @@ const attributeDelete = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeDeleteMutation = TypedMutation<
|
export const useAttributeDeleteMutation = makeMutation<
|
||||||
AttributeDelete,
|
AttributeDelete,
|
||||||
AttributeDeleteVariables
|
AttributeDeleteVariables
|
||||||
>(attributeDelete);
|
>(attributeDelete);
|
||||||
|
@ -80,7 +80,7 @@ export const attributeUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeUpdateMutation = TypedMutation<
|
export const useAttributeUpdateMutation = makeMutation<
|
||||||
AttributeUpdate,
|
AttributeUpdate,
|
||||||
AttributeUpdateVariables
|
AttributeUpdateVariables
|
||||||
>(attributeUpdateMutation);
|
>(attributeUpdateMutation);
|
||||||
|
@ -99,7 +99,7 @@ const attributeValueDelete = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeValueDeleteMutation = TypedMutation<
|
export const useAttributeValueDeleteMutation = makeMutation<
|
||||||
AttributeValueDelete,
|
AttributeValueDelete,
|
||||||
AttributeValueDeleteVariables
|
AttributeValueDeleteVariables
|
||||||
>(attributeValueDelete);
|
>(attributeValueDelete);
|
||||||
|
@ -118,7 +118,7 @@ export const attributeValueUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeValueUpdateMutation = TypedMutation<
|
export const useAttributeValueUpdateMutation = makeMutation<
|
||||||
AttributeValueUpdate,
|
AttributeValueUpdate,
|
||||||
AttributeValueUpdateVariables
|
AttributeValueUpdateVariables
|
||||||
>(attributeValueUpdateMutation);
|
>(attributeValueUpdateMutation);
|
||||||
|
@ -137,7 +137,7 @@ export const attributeValueCreateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeValueCreateMutation = TypedMutation<
|
export const useAttributeValueCreateMutation = makeMutation<
|
||||||
AttributeValueCreate,
|
AttributeValueCreate,
|
||||||
AttributeValueCreateVariables
|
AttributeValueCreateVariables
|
||||||
>(attributeValueCreateMutation);
|
>(attributeValueCreateMutation);
|
||||||
|
@ -156,7 +156,7 @@ export const attributeCreateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeCreateMutation = TypedMutation<
|
export const useAttributeCreateMutation = makeMutation<
|
||||||
AttributeCreate,
|
AttributeCreate,
|
||||||
AttributeCreateVariables
|
AttributeCreateVariables
|
||||||
>(attributeCreateMutation);
|
>(attributeCreateMutation);
|
||||||
|
@ -177,7 +177,7 @@ const attributeValueReorderMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeValueReorderMutation = TypedMutation<
|
export const useAttributeValueReorderMutation = makeMutation<
|
||||||
AttributeValueReorder,
|
AttributeValueReorder,
|
||||||
AttributeValueReorderVariables
|
AttributeValueReorderVariables
|
||||||
>(attributeValueReorderMutation);
|
>(attributeValueReorderMutation);
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { pageInfoFragment } from "@saleor/fragments/pageInfo";
|
||||||
import makeQuery from "@saleor/hooks/makeQuery";
|
import makeQuery from "@saleor/hooks/makeQuery";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedQuery } from "../queries";
|
|
||||||
import {
|
import {
|
||||||
AttributeDetails,
|
AttributeDetails,
|
||||||
AttributeDetailsVariables
|
AttributeDetailsVariables
|
||||||
|
@ -21,7 +20,7 @@ const attributeDetails = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AttributeDetailsQuery = TypedQuery<
|
export const useAttributeDetailsQuery = makeQuery<
|
||||||
AttributeDetails,
|
AttributeDetails,
|
||||||
AttributeDetailsVariables
|
AttributeDetailsVariables
|
||||||
>(attributeDetails);
|
>(attributeDetails);
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType, Produ
|
||||||
// GraphQL mutation operation: AttributeCreate
|
// GraphQL mutation operation: AttributeCreate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeCreate_attributeCreate_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeCreate_attributeCreate_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeCreate_attributeCreate_attribute_values {
|
export interface AttributeCreate_attributeCreate_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeCreate_attributeCreate_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeCreate_attributeCreate_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeCreate_attributeCreate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType } from "./../../types/global
|
||||||
// GraphQL query operation: AttributeDetails
|
// GraphQL query operation: AttributeDetails
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeDetails_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeDetails_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeDetails_attribute_values {
|
export interface AttributeDetails_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeDetails_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeDetails_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeDetails_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType, Produ
|
||||||
// GraphQL mutation operation: AttributeUpdate
|
// GraphQL mutation operation: AttributeUpdate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeUpdate_attributeUpdate_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeUpdate_attributeUpdate_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeUpdate_attributeUpdate_attribute_values {
|
export interface AttributeUpdate_attributeUpdate_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeUpdate_attributeUpdate_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeUpdate_attributeUpdate_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeUpdate_attributeUpdate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType,
|
||||||
// GraphQL mutation operation: AttributeValueCreate
|
// GraphQL mutation operation: AttributeValueCreate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeValueCreate_attributeValueCreate_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeValueCreate_attributeValueCreate_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeValueCreate_attributeValueCreate_attribute_values {
|
export interface AttributeValueCreate_attributeValueCreate_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeValueCreate_attributeValueCreate_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeValueCreate_attributeValueCreate_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeValueCreate_attributeValueCreate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./
|
||||||
// GraphQL mutation operation: AttributeValueDelete
|
// GraphQL mutation operation: AttributeValueDelete
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeValueDelete_attributeValueDelete_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeValueDelete_attributeValueDelete_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeValueDelete_attributeValueDelete_attribute_values {
|
export interface AttributeValueDelete_attributeValueDelete_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeValueDelete_attributeValueDelete_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeValueDelete_attributeValueDelete_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeValueDelete_attributeValueDelete_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType,
|
||||||
// GraphQL mutation operation: AttributeValueUpdate
|
// GraphQL mutation operation: AttributeValueUpdate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeValueUpdate_attributeValueUpdate_attribute_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeValueUpdate_attributeValueUpdate_attribute_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeValueUpdate_attributeValueUpdate_attribute_values {
|
export interface AttributeValueUpdate_attributeValueUpdate_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeValueUpdate_attributeValueUpdate_attribute_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeValueUpdate_attributeValueUpdate_attribute_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { maybe } from "@saleor/misc";
|
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import { ProductErrorCode } from "@saleor/types/globalTypes";
|
import { ProductErrorCode } from "@saleor/types/globalTypes";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
@ -21,8 +21,7 @@ import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDia
|
||||||
import AttributeValueEditDialog, {
|
import AttributeValueEditDialog, {
|
||||||
AttributeValueEditDialogFormData
|
AttributeValueEditDialogFormData
|
||||||
} from "../../components/AttributeValueEditDialog";
|
} from "../../components/AttributeValueEditDialog";
|
||||||
import { AttributeCreateMutation } from "../../mutations";
|
import { useAttributeCreateMutation } from "../../mutations";
|
||||||
import { AttributeCreate } from "../../types/AttributeCreate";
|
|
||||||
import {
|
import {
|
||||||
attributeAddUrl,
|
attributeAddUrl,
|
||||||
AttributeAddUrlDialog,
|
AttributeAddUrlDialog,
|
||||||
|
@ -60,6 +59,20 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [attributeCreate, attributeCreateOpts] = useAttributeCreateMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.attributeCreate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Successfully created attribute"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(attributeUrl(data.attributeCreate.attribute.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const id = params.id ? parseInt(params.id, 0) : undefined;
|
const id = params.id ? parseInt(params.id, 0) : undefined;
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
@ -73,17 +86,6 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
setValues(remove(values[params.id], values, areValuesEqual));
|
setValues(remove(values[params.id], values, areValuesEqual));
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
const handleCreate = (data: AttributeCreate) => {
|
|
||||||
if (data.attributeCreate.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Successfully created attribute"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
navigate(attributeUrl(data.attributeCreate.attribute.id));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleValueUpdate = (input: AttributeValueEditDialogFormData) => {
|
const handleValueUpdate = (input: AttributeValueEditDialogFormData) => {
|
||||||
if (isSelected(input, values, areValuesEqual)) {
|
if (isSelected(input, values, areValuesEqual)) {
|
||||||
setValueErrors([attributeValueAlreadyExistsError]);
|
setValueErrors([attributeValueAlreadyExistsError]);
|
||||||
|
@ -104,87 +106,83 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
setValues(move(values[oldIndex], values, areValuesEqual, newIndex));
|
setValues(move(values[oldIndex], values, areValuesEqual, newIndex));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttributeCreateMutation onCompleted={handleCreate}>
|
<>
|
||||||
{(attributeCreate, attributeCreateOpts) => (
|
<AttributePage
|
||||||
|
attribute={null}
|
||||||
|
disabled={attributeCreateOpts.loading}
|
||||||
|
errors={attributeCreateOpts.data?.attributeCreate.errors || []}
|
||||||
|
onBack={() => navigate(attributeListUrl())}
|
||||||
|
onDelete={undefined}
|
||||||
|
onSubmit={input =>
|
||||||
|
attributeCreate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
...input,
|
||||||
|
storefrontSearchPosition: parseInt(
|
||||||
|
input.storefrontSearchPosition,
|
||||||
|
0
|
||||||
|
),
|
||||||
|
values: values.map(value => ({
|
||||||
|
name: value.name
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onValueAdd={() => openModal("add-value")}
|
||||||
|
onValueDelete={id =>
|
||||||
|
openModal("remove-value", {
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onValueReorder={handleValueReorder}
|
||||||
|
onValueUpdate={id =>
|
||||||
|
openModal("edit-value", {
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
saveButtonBarState={attributeCreateOpts.status}
|
||||||
|
values={values.map((value, valueIndex) => ({
|
||||||
|
__typename: "AttributeValue" as "AttributeValue",
|
||||||
|
id: valueIndex.toString(),
|
||||||
|
slug: slugify(value.name).toLowerCase(),
|
||||||
|
sortOrder: valueIndex,
|
||||||
|
type: null,
|
||||||
|
value: null,
|
||||||
|
...value
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<AttributeValueEditDialog
|
||||||
|
attributeValue={null}
|
||||||
|
confirmButtonState="default"
|
||||||
|
disabled={false}
|
||||||
|
errors={valueErrors}
|
||||||
|
open={params.action === "add-value"}
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleValueCreate}
|
||||||
|
/>
|
||||||
|
{values.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<AttributePage
|
<AttributeValueDeleteDialog
|
||||||
attribute={null}
|
attributeName={undefined}
|
||||||
disabled={false}
|
open={params.action === "remove-value"}
|
||||||
errors={attributeCreateOpts.data?.attributeCreate.errors || []}
|
name={getStringOrPlaceholder(values[id].name)}
|
||||||
onBack={() => navigate(attributeListUrl())}
|
confirmButtonState="default"
|
||||||
onDelete={undefined}
|
onClose={closeModal}
|
||||||
onSubmit={input =>
|
onConfirm={handleValueDelete}
|
||||||
attributeCreate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
...input,
|
|
||||||
storefrontSearchPosition: parseInt(
|
|
||||||
input.storefrontSearchPosition,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
values: values.map(value => ({
|
|
||||||
name: value.name
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onValueAdd={() => openModal("add-value")}
|
|
||||||
onValueDelete={id =>
|
|
||||||
openModal("remove-value", {
|
|
||||||
id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onValueReorder={handleValueReorder}
|
|
||||||
onValueUpdate={id =>
|
|
||||||
openModal("edit-value", {
|
|
||||||
id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
saveButtonBarState={attributeCreateOpts.status}
|
|
||||||
values={values.map((value, valueIndex) => ({
|
|
||||||
__typename: "AttributeValue" as "AttributeValue",
|
|
||||||
id: valueIndex.toString(),
|
|
||||||
slug: slugify(value.name).toLowerCase(),
|
|
||||||
sortOrder: valueIndex,
|
|
||||||
type: null,
|
|
||||||
value: null,
|
|
||||||
...value
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
<AttributeValueEditDialog
|
<AttributeValueEditDialog
|
||||||
attributeValue={null}
|
attributeValue={values[params.id]}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
errors={valueErrors}
|
errors={valueErrors}
|
||||||
open={params.action === "add-value"}
|
open={params.action === "edit-value"}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={handleValueCreate}
|
onSubmit={handleValueUpdate}
|
||||||
/>
|
/>
|
||||||
{values.length > 0 && (
|
|
||||||
<>
|
|
||||||
<AttributeValueDeleteDialog
|
|
||||||
attributeName={undefined}
|
|
||||||
open={params.action === "remove-value"}
|
|
||||||
name={maybe(() => values[id].name, "...")}
|
|
||||||
confirmButtonState="default"
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={handleValueDelete}
|
|
||||||
/>
|
|
||||||
<AttributeValueEditDialog
|
|
||||||
attributeValue={maybe(() => values[params.id])}
|
|
||||||
confirmButtonState="default"
|
|
||||||
disabled={false}
|
|
||||||
errors={valueErrors}
|
|
||||||
open={params.action === "edit-value"}
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleValueUpdate}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AttributeCreateMutation>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
AttributeDetails.displayName = "AttributeDetails";
|
AttributeDetails.displayName = "AttributeDetails";
|
||||||
|
|
|
@ -5,29 +5,30 @@ import { maybe } from "@saleor/misc";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import { getProductErrorMessage } from "@saleor/utils/errors";
|
import { getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
import { move } from "@saleor/utils/lists";
|
import { move } from "@saleor/utils/lists";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import AttributeDeleteDialog from "../../components/AttributeDeleteDialog";
|
import AttributeDeleteDialog from "../../components/AttributeDeleteDialog";
|
||||||
import AttributePage from "../../components/AttributePage";
|
import AttributePage, {
|
||||||
|
AttributePageFormData
|
||||||
|
} from "../../components/AttributePage";
|
||||||
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
|
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
|
||||||
import AttributeValueEditDialog from "../../components/AttributeValueEditDialog";
|
import AttributeValueEditDialog from "../../components/AttributeValueEditDialog";
|
||||||
import {
|
import {
|
||||||
AttributeDeleteMutation,
|
useAttributeDeleteMutation,
|
||||||
AttributeUpdateMutation,
|
useAttributeUpdateMutation,
|
||||||
AttributeValueCreateMutation,
|
useAttributeValueCreateMutation,
|
||||||
AttributeValueDeleteMutation,
|
useAttributeValueDeleteMutation,
|
||||||
AttributeValueReorderMutation,
|
useAttributeValueReorderMutation,
|
||||||
AttributeValueUpdateMutation
|
useAttributeValueUpdateMutation
|
||||||
} from "../../mutations";
|
} from "../../mutations";
|
||||||
import { AttributeDetailsQuery } from "../../queries";
|
import { useAttributeDetailsQuery } from "../../queries";
|
||||||
import { AttributeDelete } from "../../types/AttributeDelete";
|
|
||||||
import { AttributeUpdate } from "../../types/AttributeUpdate";
|
|
||||||
import { AttributeValueCreate } from "../../types/AttributeValueCreate";
|
|
||||||
import { AttributeValueDelete } from "../../types/AttributeValueDelete";
|
|
||||||
import { AttributeValueReorder } from "../../types/AttributeValueReorder";
|
|
||||||
import { AttributeValueUpdate } from "../../types/AttributeValueUpdate";
|
|
||||||
import {
|
import {
|
||||||
attributeListUrl,
|
attributeListUrl,
|
||||||
attributeUrl,
|
attributeUrl,
|
||||||
|
@ -44,285 +45,258 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
AttributeUrlDialog,
|
AttributeUrlDialog,
|
||||||
AttributeUrlQueryParams
|
AttributeUrlQueryParams
|
||||||
>(navigate, params => attributeUrl(id, params), params);
|
>(navigate, params => attributeUrl(id, params), params);
|
||||||
|
|
||||||
const handleDelete = (data: AttributeDelete) => {
|
const { data, loading } = useAttributeDetailsQuery({
|
||||||
if (data.attributeDelete.errors.length === 0) {
|
variables: {
|
||||||
notify({
|
id
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Attribute deleted"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
navigate(attributeListUrl());
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const handleValueDelete = (data: AttributeValueDelete) => {
|
|
||||||
if (data.attributeValueDelete.errors.length === 0) {
|
const [attributeDelete, attributeDeleteOpts] = useAttributeDeleteMutation({
|
||||||
notify({
|
onCompleted: data => {
|
||||||
status: "success",
|
if (data.attributeDelete.errors.length === 0) {
|
||||||
text: intl.formatMessage({
|
notify({
|
||||||
defaultMessage: "Value deleted",
|
status: "success",
|
||||||
description: "attribute value deleted"
|
text: intl.formatMessage({
|
||||||
})
|
defaultMessage: "Attribute deleted"
|
||||||
});
|
})
|
||||||
closeModal();
|
});
|
||||||
|
navigate(attributeListUrl());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const handleUpdate = (data: AttributeUpdate) => {
|
|
||||||
if (data.attributeUpdate.errors.length === 0) {
|
const [
|
||||||
notify({
|
attributeValueDelete,
|
||||||
status: "success",
|
attributeValueDeleteOpts
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
] = useAttributeValueDeleteMutation({
|
||||||
});
|
onCompleted: data => {
|
||||||
|
if (data.attributeValueDelete.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Value deleted",
|
||||||
|
description: "attribute value deleted"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const handleValueUpdate = (data: AttributeValueUpdate) => {
|
|
||||||
if (data.attributeValueUpdate.errors.length === 0) {
|
const [
|
||||||
notify({
|
attributeValueUpdate,
|
||||||
status: "success",
|
attributeValueUpdateOpts
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
] = useAttributeValueUpdateMutation({
|
||||||
});
|
onCompleted: data => {
|
||||||
closeModal();
|
if (data.attributeValueUpdate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const handleValueCreate = (data: AttributeValueCreate) => {
|
|
||||||
if (data.attributeValueCreate.errors.length === 0) {
|
const [attributeUpdate, attributeUpdateOpts] = useAttributeUpdateMutation({
|
||||||
notify({
|
onCompleted: data => {
|
||||||
status: "success",
|
if (data.attributeUpdate.errors.length === 0) {
|
||||||
text: intl.formatMessage({
|
notify({
|
||||||
defaultMessage: "Added new value",
|
status: "success",
|
||||||
description: "added new attribute value"
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
})
|
});
|
||||||
});
|
}
|
||||||
closeModal();
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
const handleValueReorderMutation = (data: AttributeValueReorder) => {
|
|
||||||
if (data.attributeReorderValues.errors.length !== 0) {
|
const [
|
||||||
notify({
|
attributeValueCreate,
|
||||||
status: "error",
|
attributeValueCreateOpts
|
||||||
text: getProductErrorMessage(
|
] = useAttributeValueCreateMutation({
|
||||||
data.attributeReorderValues.errors[0],
|
onCompleted: data => {
|
||||||
intl
|
if (data.attributeValueCreate.errors.length === 0) {
|
||||||
)
|
notify({
|
||||||
});
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Added new value",
|
||||||
|
description: "added new attribute value"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [attributeValueReorder] = useAttributeValueReorderMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.attributeReorderValues.errors.length !== 0) {
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: getProductErrorMessage(
|
||||||
|
data.attributeReorderValues.errors[0],
|
||||||
|
intl
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleValueReorder = ({ newIndex, oldIndex }: ReorderEvent) =>
|
||||||
|
attributeValueReorder({
|
||||||
|
optimisticResponse: {
|
||||||
|
attributeReorderValues: {
|
||||||
|
__typename: "AttributeReorderValues",
|
||||||
|
attribute: {
|
||||||
|
...data.attribute,
|
||||||
|
values: move(
|
||||||
|
data.attribute.values[oldIndex],
|
||||||
|
data.attribute.values,
|
||||||
|
(a, b) => a.id === b.id,
|
||||||
|
newIndex
|
||||||
|
)
|
||||||
|
},
|
||||||
|
errors: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
move: {
|
||||||
|
id: data.attribute.values[oldIndex].id,
|
||||||
|
sortOrder: newIndex - oldIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdate = async (data: AttributePageFormData) => {
|
||||||
|
const input = {
|
||||||
|
...data,
|
||||||
|
inputType: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
privateMetadata: undefined,
|
||||||
|
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await attributeUpdate({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data.attributeUpdate.errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
data?.attribute,
|
||||||
|
handleUpdate,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttributeDetailsQuery variables={{ id }}>
|
<>
|
||||||
{({ data, loading }) => (
|
<AttributePage
|
||||||
<AttributeDeleteMutation onCompleted={handleDelete}>
|
attribute={maybe(() => data.attribute)}
|
||||||
{(attributeDelete, attributeDeleteOpts) => (
|
disabled={loading}
|
||||||
<AttributeValueDeleteMutation onCompleted={handleValueDelete}>
|
errors={attributeUpdateOpts.data?.attributeUpdate.errors || []}
|
||||||
{(attributeValueDelete, attributeValueDeleteOpts) => (
|
onBack={() => navigate(attributeListUrl())}
|
||||||
<AttributeUpdateMutation onCompleted={handleUpdate}>
|
onDelete={() => openModal("remove")}
|
||||||
{(attributeUpdate, attributeUpdateOpts) => (
|
onSubmit={handleSubmit}
|
||||||
<AttributeValueUpdateMutation
|
onValueAdd={() => openModal("add-value")}
|
||||||
onCompleted={handleValueUpdate}
|
onValueDelete={id =>
|
||||||
>
|
openModal("remove-value", {
|
||||||
{(attributeValueUpdate, attributeValueUpdateOpts) => (
|
id
|
||||||
<AttributeValueCreateMutation
|
})
|
||||||
onCompleted={handleValueCreate}
|
}
|
||||||
>
|
onValueReorder={handleValueReorder}
|
||||||
{(attributeValueCreate, attributeValueCreateOpts) => (
|
onValueUpdate={id =>
|
||||||
<AttributeValueReorderMutation
|
openModal("edit-value", {
|
||||||
onCompleted={handleValueReorderMutation}
|
id
|
||||||
>
|
})
|
||||||
{attributeValueReorder => {
|
}
|
||||||
const handleValueReorder = ({
|
saveButtonBarState={attributeUpdateOpts.status}
|
||||||
newIndex,
|
values={maybe(() => data.attribute.values)}
|
||||||
oldIndex
|
/>
|
||||||
}: ReorderEvent) =>
|
<AttributeDeleteDialog
|
||||||
attributeValueReorder({
|
open={params.action === "remove"}
|
||||||
optimisticResponse: {
|
name={maybe(() => data.attribute.name, "...")}
|
||||||
attributeReorderValues: {
|
confirmButtonState={attributeDeleteOpts.status}
|
||||||
__typename: "AttributeReorderValues",
|
onClose={closeModal}
|
||||||
attribute: {
|
onConfirm={() =>
|
||||||
...data.attribute,
|
attributeDelete({
|
||||||
values: move(
|
variables: {
|
||||||
data.attribute.values[oldIndex],
|
id
|
||||||
data.attribute.values,
|
}
|
||||||
(a, b) => a.id === b.id,
|
})
|
||||||
newIndex
|
}
|
||||||
)
|
/>
|
||||||
},
|
<AttributeValueDeleteDialog
|
||||||
errors: []
|
attributeName={maybe(() => data.attribute.name, "...")}
|
||||||
}
|
open={params.action === "remove-value"}
|
||||||
},
|
name={maybe(
|
||||||
variables: {
|
() =>
|
||||||
id,
|
data.attribute.values.find(value => params.id === value.id).name,
|
||||||
move: {
|
"..."
|
||||||
id: data.attribute.values[oldIndex].id,
|
)}
|
||||||
sortOrder: newIndex - oldIndex
|
useName={true}
|
||||||
}
|
confirmButtonState={attributeValueDeleteOpts.status}
|
||||||
}
|
onClose={closeModal}
|
||||||
});
|
onConfirm={() =>
|
||||||
|
attributeValueDelete({
|
||||||
return (
|
variables: {
|
||||||
<>
|
id: params.id
|
||||||
<AttributePage
|
}
|
||||||
attribute={maybe(() => data.attribute)}
|
})
|
||||||
disabled={loading}
|
}
|
||||||
errors={
|
/>
|
||||||
attributeUpdateOpts.data
|
<AttributeValueEditDialog
|
||||||
?.attributeUpdate.errors || []
|
attributeValue={null}
|
||||||
}
|
confirmButtonState={attributeValueCreateOpts.status}
|
||||||
onBack={() =>
|
disabled={loading}
|
||||||
navigate(attributeListUrl())
|
errors={
|
||||||
}
|
attributeValueCreateOpts.data?.attributeValueCreate.errors || []
|
||||||
onDelete={() => openModal("remove")}
|
}
|
||||||
onSubmit={data => {
|
open={params.action === "add-value"}
|
||||||
const input = {
|
onClose={closeModal}
|
||||||
...data,
|
onSubmit={input =>
|
||||||
inputType: undefined
|
attributeValueCreate({
|
||||||
};
|
variables: {
|
||||||
|
id,
|
||||||
attributeUpdate({
|
input
|
||||||
variables: {
|
}
|
||||||
id,
|
})
|
||||||
input: {
|
}
|
||||||
...input,
|
/>
|
||||||
storefrontSearchPosition: parseInt(
|
<AttributeValueEditDialog
|
||||||
input.storefrontSearchPosition,
|
attributeValue={maybe(() =>
|
||||||
0
|
data.attribute.values.find(value => params.id === value.id)
|
||||||
)
|
)}
|
||||||
}
|
confirmButtonState={attributeValueUpdateOpts.status}
|
||||||
}
|
disabled={loading}
|
||||||
});
|
errors={
|
||||||
}}
|
attributeValueUpdateOpts.data?.attributeValueUpdate.errors || []
|
||||||
onValueAdd={() => openModal("add-value")}
|
}
|
||||||
onValueDelete={id =>
|
open={params.action === "edit-value"}
|
||||||
openModal("remove-value", {
|
onClose={closeModal}
|
||||||
id
|
onSubmit={input =>
|
||||||
})
|
attributeValueUpdate({
|
||||||
}
|
variables: {
|
||||||
onValueReorder={handleValueReorder}
|
id: data.attribute.values.find(value => params.id === value.id)
|
||||||
onValueUpdate={id =>
|
.id,
|
||||||
openModal("edit-value", {
|
input
|
||||||
id
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
saveButtonBarState={
|
/>
|
||||||
attributeUpdateOpts.status
|
</>
|
||||||
}
|
|
||||||
values={maybe(
|
|
||||||
() => data.attribute.values
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<AttributeDeleteDialog
|
|
||||||
open={params.action === "remove"}
|
|
||||||
name={maybe(
|
|
||||||
() => data.attribute.name,
|
|
||||||
"..."
|
|
||||||
)}
|
|
||||||
confirmButtonState={
|
|
||||||
attributeDeleteOpts.status
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
attributeDelete({
|
|
||||||
variables: {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AttributeValueDeleteDialog
|
|
||||||
attributeName={maybe(
|
|
||||||
() => data.attribute.name,
|
|
||||||
"..."
|
|
||||||
)}
|
|
||||||
open={params.action === "remove-value"}
|
|
||||||
name={maybe(
|
|
||||||
() =>
|
|
||||||
data.attribute.values.find(
|
|
||||||
value => params.id === value.id
|
|
||||||
).name,
|
|
||||||
"..."
|
|
||||||
)}
|
|
||||||
useName={true}
|
|
||||||
confirmButtonState={
|
|
||||||
attributeValueDeleteOpts.status
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
attributeValueDelete({
|
|
||||||
variables: {
|
|
||||||
id: params.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AttributeValueEditDialog
|
|
||||||
attributeValue={null}
|
|
||||||
confirmButtonState={
|
|
||||||
attributeValueCreateOpts.status
|
|
||||||
}
|
|
||||||
disabled={loading}
|
|
||||||
errors={
|
|
||||||
attributeValueCreateOpts.data
|
|
||||||
?.attributeValueCreate.errors || []
|
|
||||||
}
|
|
||||||
open={params.action === "add-value"}
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={input =>
|
|
||||||
attributeValueCreate({
|
|
||||||
variables: {
|
|
||||||
id,
|
|
||||||
input
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<AttributeValueEditDialog
|
|
||||||
attributeValue={maybe(() =>
|
|
||||||
data.attribute.values.find(
|
|
||||||
value => params.id === value.id
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
confirmButtonState={
|
|
||||||
attributeValueUpdateOpts.status
|
|
||||||
}
|
|
||||||
disabled={loading}
|
|
||||||
errors={
|
|
||||||
attributeValueUpdateOpts.data
|
|
||||||
?.attributeValueUpdate.errors || []
|
|
||||||
}
|
|
||||||
open={params.action === "edit-value"}
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={input =>
|
|
||||||
attributeValueUpdate({
|
|
||||||
variables: {
|
|
||||||
id: data.attribute.values.find(
|
|
||||||
value => params.id === value.id
|
|
||||||
).id,
|
|
||||||
input
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</AttributeValueReorderMutation>
|
|
||||||
)}
|
|
||||||
</AttributeValueCreateMutation>
|
|
||||||
)}
|
|
||||||
</AttributeValueUpdateMutation>
|
|
||||||
)}
|
|
||||||
</AttributeUpdateMutation>
|
|
||||||
)}
|
|
||||||
</AttributeValueDeleteMutation>
|
|
||||||
)}
|
|
||||||
</AttributeDeleteMutation>
|
|
||||||
)}
|
|
||||||
</AttributeDetailsQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
AttributeDetails.displayName = "AttributeDetails";
|
AttributeDetails.displayName = "AttributeDetails";
|
||||||
|
|
|
@ -32,9 +32,8 @@ import useBulkActions from "../../../hooks/useBulkActions";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
||||||
import AttributeListPage from "../../components/AttributeListPage";
|
import AttributeListPage from "../../components/AttributeListPage";
|
||||||
import { AttributeBulkDeleteMutation } from "../../mutations";
|
import { useAttributeBulkDeleteMutation } from "../../mutations";
|
||||||
import { useAttributeListQuery } from "../../queries";
|
import { useAttributeListQuery } from "../../queries";
|
||||||
import { AttributeBulkDelete } from "../../types/AttributeBulkDelete";
|
|
||||||
import {
|
import {
|
||||||
attributeAddUrl,
|
attributeAddUrl,
|
||||||
attributeListUrl,
|
attributeListUrl,
|
||||||
|
@ -72,6 +71,26 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
variables: queryVariables
|
variables: queryVariables
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
attributeBulkDelete,
|
||||||
|
attributeBulkDeleteOpts
|
||||||
|
] = useAttributeBulkDeleteMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.attributeBulkDelete.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Attributes successfully delete",
|
||||||
|
description: "deleted multiple attributes"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
const currentTab =
|
const currentTab =
|
||||||
|
@ -125,96 +144,73 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBulkDelete = (data: AttributeBulkDelete) => {
|
|
||||||
if (data.attributeBulkDelete.errors.length === 0) {
|
|
||||||
closeModal();
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Attributes successfully delete",
|
|
||||||
description: "deleted multiple attributes"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
reset();
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
const handleSort = createSortHandler(navigate, attributeListUrl, params);
|
||||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AttributeBulkDeleteMutation onCompleted={handleBulkDelete}>
|
<>
|
||||||
{(attributeBulkDelete, attributeBulkDeleteOpts) => (
|
<AttributeListPage
|
||||||
<>
|
attributes={maybe(() => data.attributes.edges.map(edge => edge.node))}
|
||||||
<AttributeListPage
|
currencySymbol={currencySymbol}
|
||||||
attributes={maybe(() =>
|
currentTab={currentTab}
|
||||||
data.attributes.edges.map(edge => edge.node)
|
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||||
)}
|
filterOpts={getFilterOpts(params)}
|
||||||
currencySymbol={currencySymbol}
|
initialSearch={params.query || ""}
|
||||||
currentTab={currentTab}
|
isChecked={isSelected}
|
||||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
onAdd={() => navigate(attributeAddUrl())}
|
||||||
filterOpts={getFilterOpts(params)}
|
onAll={resetFilters}
|
||||||
initialSearch={params.query || ""}
|
onBack={() => navigate(configurationMenuUrl)}
|
||||||
isChecked={isSelected}
|
onFilterChange={changeFilters}
|
||||||
onAdd={() => navigate(attributeAddUrl())}
|
onNextPage={loadNextPage}
|
||||||
onAll={resetFilters}
|
onPreviousPage={loadPreviousPage}
|
||||||
onBack={() => navigate(configurationMenuUrl)}
|
onRowClick={id => () => navigate(attributeUrl(id))}
|
||||||
onFilterChange={changeFilters}
|
onSearchChange={handleSearchChange}
|
||||||
onNextPage={loadNextPage}
|
onSort={handleSort}
|
||||||
onPreviousPage={loadPreviousPage}
|
onTabChange={handleTabChange}
|
||||||
onRowClick={id => () => navigate(attributeUrl(id))}
|
onTabDelete={() => openModal("delete-search")}
|
||||||
onSearchChange={handleSearchChange}
|
onTabSave={() => openModal("save-search")}
|
||||||
onSort={handleSort}
|
pageInfo={pageInfo}
|
||||||
onTabChange={handleTabChange}
|
selected={listElements.length}
|
||||||
onTabDelete={() => openModal("delete-search")}
|
sort={getSortParams(params)}
|
||||||
onTabSave={() => openModal("save-search")}
|
tabs={tabs.map(tab => tab.name)}
|
||||||
pageInfo={pageInfo}
|
toggle={toggle}
|
||||||
selected={listElements.length}
|
toggleAll={toggleAll}
|
||||||
sort={getSortParams(params)}
|
toolbar={
|
||||||
tabs={tabs.map(tab => tab.name)}
|
<IconButton
|
||||||
toggle={toggle}
|
color="primary"
|
||||||
toggleAll={toggleAll}
|
onClick={() =>
|
||||||
toolbar={
|
openModal("remove", {
|
||||||
<IconButton
|
ids: listElements
|
||||||
color="primary"
|
})
|
||||||
onClick={() =>
|
|
||||||
openModal("remove", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
<AttributeBulkDeleteDialog
|
<DeleteIcon />
|
||||||
confirmButtonState={attributeBulkDeleteOpts.status}
|
</IconButton>
|
||||||
open={
|
}
|
||||||
params.action === "remove" && maybe(() => params.ids.length > 0)
|
/>
|
||||||
}
|
<AttributeBulkDeleteDialog
|
||||||
onConfirm={() =>
|
confirmButtonState={attributeBulkDeleteOpts.status}
|
||||||
attributeBulkDelete({ variables: { ids: params.ids } })
|
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||||
}
|
onConfirm={() =>
|
||||||
onClose={closeModal}
|
attributeBulkDelete({ variables: { ids: params.ids } })
|
||||||
quantity={maybe(() => params.ids.length)}
|
}
|
||||||
/>
|
onClose={closeModal}
|
||||||
<SaveFilterTabDialog
|
quantity={maybe(() => params.ids.length)}
|
||||||
open={params.action === "save-search"}
|
/>
|
||||||
confirmButtonState="default"
|
<SaveFilterTabDialog
|
||||||
onClose={closeModal}
|
open={params.action === "save-search"}
|
||||||
onSubmit={handleTabSave}
|
confirmButtonState="default"
|
||||||
/>
|
onClose={closeModal}
|
||||||
<DeleteFilterTabDialog
|
onSubmit={handleTabSave}
|
||||||
open={params.action === "delete-search"}
|
/>
|
||||||
confirmButtonState="default"
|
<DeleteFilterTabDialog
|
||||||
onClose={closeModal}
|
open={params.action === "delete-search"}
|
||||||
onSubmit={handleTabDelete}
|
confirmButtonState="default"
|
||||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
onClose={closeModal}
|
||||||
/>
|
onSubmit={handleTabDelete}
|
||||||
</>
|
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||||
)}
|
/>
|
||||||
</AttributeBulkDeleteMutation>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
AttributeList.displayName = "AttributeList";
|
AttributeList.displayName = "AttributeList";
|
||||||
|
|
|
@ -6,12 +6,16 @@ import CardTitle from "@saleor/components/CardTitle";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import { RawDraftContentState } from "draft-js";
|
import { RawDraftContentState } from "draft-js";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
@ -28,7 +32,7 @@ import {
|
||||||
import CategoryBackground from "../CategoryBackground";
|
import CategoryBackground from "../CategoryBackground";
|
||||||
import CategoryProducts from "../CategoryProducts";
|
import CategoryProducts from "../CategoryProducts";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -100,144 +104,174 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
||||||
toggleAll
|
toggleAll
|
||||||
}: CategoryUpdatePageProps) => {
|
}: CategoryUpdatePageProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const initialData: FormData = category
|
const initialData: FormData = category
|
||||||
? {
|
? {
|
||||||
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
|
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
|
||||||
description: maybe(() => JSON.parse(category.descriptionJson)),
|
description: maybe(() => JSON.parse(category.descriptionJson)),
|
||||||
|
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: category.name || "",
|
name: category.name || "",
|
||||||
|
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
seoDescription: category.seoDescription || "",
|
seoDescription: category.seoDescription || "",
|
||||||
seoTitle: category.seoTitle || ""
|
seoTitle: category.seoTitle || ""
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
backgroundImageAlt: "",
|
backgroundImageAlt: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
metadata: undefined,
|
||||||
name: "",
|
name: "",
|
||||||
|
privateMetadata: undefined,
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: ""
|
seoTitle: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (data: FormData) => {
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
onSubmit({
|
||||||
|
...data,
|
||||||
|
metadata,
|
||||||
|
privateMetadata
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={onSubmit} initial={initialData} confirmLeave>
|
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
|
||||||
{({ data, change, submit, hasChanged }) => (
|
{({ data, change, submit, hasChanged }) => {
|
||||||
<Container>
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
<AppHeader onBack={onBack}>
|
|
||||||
{intl.formatMessage(sectionNames.categories)}
|
return (
|
||||||
</AppHeader>
|
<Container>
|
||||||
<PageHeader title={category ? category.name : undefined} />
|
<AppHeader onBack={onBack}>
|
||||||
<CategoryDetailsForm
|
{intl.formatMessage(sectionNames.categories)}
|
||||||
category={category}
|
</AppHeader>
|
||||||
data={data}
|
<PageHeader title={category ? category.name : undefined} />
|
||||||
disabled={disabled}
|
<CategoryDetailsForm
|
||||||
errors={errors}
|
category={category}
|
||||||
onChange={change}
|
data={data}
|
||||||
/>
|
disabled={disabled}
|
||||||
<CardSpacer />
|
errors={errors}
|
||||||
<CategoryBackground
|
onChange={change}
|
||||||
data={data}
|
/>
|
||||||
onImageUpload={onImageUpload}
|
<CardSpacer />
|
||||||
onImageDelete={onImageDelete}
|
<CategoryBackground
|
||||||
image={maybe(() => category.backgroundImage)}
|
data={data}
|
||||||
onChange={change}
|
onImageUpload={onImageUpload}
|
||||||
/>
|
onImageDelete={onImageDelete}
|
||||||
<CardSpacer />
|
image={maybe(() => category.backgroundImage)}
|
||||||
<SeoForm
|
onChange={change}
|
||||||
helperText={intl.formatMessage({
|
/>
|
||||||
defaultMessage:
|
<CardSpacer />
|
||||||
"Add search engine title and description to make this category easier to find"
|
<SeoForm
|
||||||
})}
|
helperText={intl.formatMessage({
|
||||||
title={data.seoTitle}
|
defaultMessage:
|
||||||
titlePlaceholder={data.name}
|
"Add search engine title and description to make this category easier to find"
|
||||||
description={data.seoDescription}
|
})}
|
||||||
descriptionPlaceholder={data.name}
|
title={data.seoTitle}
|
||||||
loading={!category}
|
titlePlaceholder={data.name}
|
||||||
onChange={change}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
descriptionPlaceholder={data.name}
|
||||||
/>
|
loading={!category}
|
||||||
<CardSpacer />
|
onChange={change}
|
||||||
<TabContainer>
|
disabled={disabled}
|
||||||
<CategoriesTab
|
/>
|
||||||
isActive={currentTab === CategoryPageTab.categories}
|
<CardSpacer />
|
||||||
changeTab={changeTab}
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
>
|
<CardSpacer />
|
||||||
<FormattedMessage
|
<TabContainer>
|
||||||
defaultMessage="Subcategories"
|
<CategoriesTab
|
||||||
description="number of subcategories in category"
|
isActive={currentTab === CategoryPageTab.categories}
|
||||||
/>
|
changeTab={changeTab}
|
||||||
</CategoriesTab>
|
>
|
||||||
<ProductsTab
|
<FormattedMessage
|
||||||
isActive={currentTab === CategoryPageTab.products}
|
defaultMessage="Subcategories"
|
||||||
changeTab={changeTab}
|
description="number of subcategories in category"
|
||||||
>
|
/>
|
||||||
<FormattedMessage
|
</CategoriesTab>
|
||||||
defaultMessage="Products"
|
<ProductsTab
|
||||||
description="number of products in category"
|
isActive={currentTab === CategoryPageTab.products}
|
||||||
/>
|
changeTab={changeTab}
|
||||||
</ProductsTab>
|
>
|
||||||
</TabContainer>
|
<FormattedMessage
|
||||||
<CardSpacer />
|
defaultMessage="Products"
|
||||||
{currentTab === CategoryPageTab.categories && (
|
description="number of products in category"
|
||||||
<Card>
|
/>
|
||||||
<CardTitle
|
</ProductsTab>
|
||||||
title={intl.formatMessage({
|
</TabContainer>
|
||||||
defaultMessage: "All Subcategories",
|
<CardSpacer />
|
||||||
description: "section header"
|
{currentTab === CategoryPageTab.categories && (
|
||||||
})}
|
<Card>
|
||||||
toolbar={
|
<CardTitle
|
||||||
<Button
|
title={intl.formatMessage({
|
||||||
color="primary"
|
defaultMessage: "All Subcategories",
|
||||||
variant="text"
|
description: "section header"
|
||||||
onClick={onAddCategory}
|
})}
|
||||||
>
|
toolbar={
|
||||||
<FormattedMessage
|
<Button
|
||||||
defaultMessage="Create subcategory"
|
color="primary"
|
||||||
description="button"
|
variant="text"
|
||||||
/>
|
onClick={onAddCategory}
|
||||||
</Button>
|
>
|
||||||
}
|
<FormattedMessage
|
||||||
/>
|
defaultMessage="Create subcategory"
|
||||||
<CategoryList
|
description="button"
|
||||||
categories={subcategories}
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CategoryList
|
||||||
|
categories={subcategories}
|
||||||
|
disabled={disabled}
|
||||||
|
isChecked={isChecked}
|
||||||
|
isRoot={false}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
selected={selected}
|
||||||
|
sort={undefined}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
toolbar={subcategoryListToolbar}
|
||||||
|
onNextPage={onNextPage}
|
||||||
|
onPreviousPage={onPreviousPage}
|
||||||
|
onRowClick={onCategoryClick}
|
||||||
|
onSort={() => undefined}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{currentTab === CategoryPageTab.products && (
|
||||||
|
<CategoryProducts
|
||||||
|
categoryName={maybe(() => category.name)}
|
||||||
|
products={products}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isChecked={isChecked}
|
|
||||||
isRoot={false}
|
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
selected={selected}
|
|
||||||
sort={undefined}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
toolbar={subcategoryListToolbar}
|
|
||||||
onNextPage={onNextPage}
|
onNextPage={onNextPage}
|
||||||
onPreviousPage={onPreviousPage}
|
onPreviousPage={onPreviousPage}
|
||||||
onRowClick={onCategoryClick}
|
onRowClick={onProductClick}
|
||||||
onSort={() => undefined}
|
onAdd={onAddProduct}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
selected={selected}
|
||||||
|
isChecked={isChecked}
|
||||||
|
toolbar={productListToolbar}
|
||||||
/>
|
/>
|
||||||
</Card>
|
)}
|
||||||
)}
|
<SaveButtonBar
|
||||||
{currentTab === CategoryPageTab.products && (
|
onCancel={onBack}
|
||||||
<CategoryProducts
|
onDelete={onDelete}
|
||||||
categoryName={maybe(() => category.name)}
|
onSave={submit}
|
||||||
products={products}
|
state={saveButtonBarState}
|
||||||
disabled={disabled}
|
disabled={disabled || !hasChanged}
|
||||||
pageInfo={pageInfo}
|
|
||||||
onNextPage={onNextPage}
|
|
||||||
onPreviousPage={onPreviousPage}
|
|
||||||
onRowClick={onProductClick}
|
|
||||||
onAdd={onAddProduct}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
selected={selected}
|
|
||||||
isChecked={isChecked}
|
|
||||||
toolbar={productListToolbar}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</Container>
|
||||||
<SaveButtonBar
|
);
|
||||||
onCancel={onBack}
|
}}
|
||||||
onDelete={onDelete}
|
|
||||||
onSave={submit}
|
|
||||||
state={saveButtonBarState}
|
|
||||||
disabled={disabled || !hasChanged}
|
|
||||||
/>
|
|
||||||
</Container>
|
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -105,11 +105,19 @@ export const category: (
|
||||||
},
|
},
|
||||||
descriptionJson: JSON.stringify(content),
|
descriptionJson: JSON.stringify(content),
|
||||||
id: "Q2F0ZWdvcnk6NA==",
|
id: "Q2F0ZWdvcnk6NA==",
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "Coffees",
|
name: "Coffees",
|
||||||
parent: {
|
parent: {
|
||||||
__typename: "Category",
|
__typename: "Category",
|
||||||
id: "Q2F0ZWdvcnk6Mw=="
|
id: "Q2F0ZWdvcnk6Mw=="
|
||||||
},
|
},
|
||||||
|
privateMetadata: [],
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
edges: [
|
edges: [
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { CategoryInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||||
// GraphQL mutation operation: CategoryCreate
|
// GraphQL mutation operation: CategoryCreate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CategoryCreate_categoryCreate_category_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryCreate_categoryCreate_category_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CategoryCreate_categoryCreate_category_backgroundImage {
|
export interface CategoryCreate_categoryCreate_category_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -22,6 +34,8 @@ export interface CategoryCreate_categoryCreate_category_parent {
|
||||||
export interface CategoryCreate_categoryCreate_category {
|
export interface CategoryCreate_categoryCreate_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (CategoryCreate_categoryCreate_category_metadata | null)[];
|
||||||
|
privateMetadata: (CategoryCreate_categoryCreate_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
// GraphQL query operation: CategoryDetails
|
// GraphQL query operation: CategoryDetails
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CategoryDetails_category_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryDetails_category_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CategoryDetails_category_backgroundImage {
|
export interface CategoryDetails_category_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -131,6 +143,8 @@ export interface CategoryDetails_category_products {
|
||||||
export interface CategoryDetails_category {
|
export interface CategoryDetails_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (CategoryDetails_category_metadata | null)[];
|
||||||
|
privateMetadata: (CategoryDetails_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { CategoryInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||||
// GraphQL mutation operation: CategoryUpdate
|
// GraphQL mutation operation: CategoryUpdate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CategoryUpdate_categoryUpdate_category_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryUpdate_categoryUpdate_category_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CategoryUpdate_categoryUpdate_category_backgroundImage {
|
export interface CategoryUpdate_categoryUpdate_category_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -22,6 +34,8 @@ export interface CategoryUpdate_categoryUpdate_category_parent {
|
||||||
export interface CategoryUpdate_categoryUpdate_category {
|
export interface CategoryUpdate_categoryUpdate_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (CategoryUpdate_categoryUpdate_category_metadata | null)[];
|
||||||
|
privateMetadata: (CategoryUpdate_categoryUpdate_category_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
|
|
|
@ -12,6 +12,11 @@ import usePaginator, {
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -22,7 +27,8 @@ import { productAddUrl, productUrl } from "../../products/urls";
|
||||||
import { CategoryInput } from "../../types/globalTypes";
|
import { CategoryInput } from "../../types/globalTypes";
|
||||||
import {
|
import {
|
||||||
CategoryPageTab,
|
CategoryPageTab,
|
||||||
CategoryUpdatePage
|
CategoryUpdatePage,
|
||||||
|
FormData
|
||||||
} from "../components/CategoryUpdatePage/CategoryUpdatePage";
|
} from "../components/CategoryUpdatePage/CategoryUpdatePage";
|
||||||
import {
|
import {
|
||||||
useCategoryBulkDeleteMutation,
|
useCategoryBulkDeleteMutation,
|
||||||
|
@ -63,6 +69,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
params.ids
|
params.ids
|
||||||
);
|
);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||||
const { data, loading, refetch } = useCategoryDetailsQuery({
|
const { data, loading, refetch } = useCategoryDetailsQuery({
|
||||||
|
@ -167,6 +175,31 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleUpdate = async (formData: FormData) => {
|
||||||
|
const result = await updateCategory({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
|
name: formData.name,
|
||||||
|
seo: {
|
||||||
|
description: formData.seoDescription,
|
||||||
|
title: formData.seoTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data.categoryUpdate.errors;
|
||||||
|
};
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
data?.category,
|
||||||
|
handleUpdate,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle title={maybe(() => data.category.name)} />
|
<WindowTitle title={maybe(() => data.category.name)} />
|
||||||
|
@ -209,22 +242,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
onPreviousPage={loadPreviousPage}
|
onPreviousPage={loadPreviousPage}
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
onProductClick={id => () => navigate(productUrl(id))}
|
onProductClick={id => () => navigate(productUrl(id))}
|
||||||
onSubmit={formData =>
|
onSubmit={handleSubmit}
|
||||||
updateCategory({
|
|
||||||
variables: {
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
|
||||||
name: formData.name,
|
|
||||||
seo: {
|
|
||||||
description: formData.seoDescription,
|
|
||||||
title: formData.seoTitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
products={maybe(() =>
|
products={maybe(() =>
|
||||||
data.category.products.edges.map(edge => edge.node)
|
data.category.products.edges.map(edge => edge.node)
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
|
@ -14,6 +16,8 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import { RawDraftContentState } from "draft-js";
|
import { RawDraftContentState } from "draft-js";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
@ -25,7 +29,7 @@ import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
||||||
import { CollectionImage } from "../CollectionImage/CollectionImage";
|
import { CollectionImage } from "../CollectionImage/CollectionImage";
|
||||||
import CollectionProducts from "../CollectionProducts/CollectionProducts";
|
import CollectionProducts from "../CollectionProducts/CollectionProducts";
|
||||||
|
|
||||||
export interface CollectionDetailsPageFormData {
|
export interface CollectionDetailsPageFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
description: RawDraftContentState;
|
description: RawDraftContentState;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -64,6 +68,24 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
}: CollectionDetailsPageProps) => {
|
}: CollectionDetailsPageProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const localizeDate = useDateLocalize();
|
const localizeDate = useDateLocalize();
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
|
const handleSubmit = (data: CollectionDetailsPageFormData) => {
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
onSubmit({
|
||||||
|
...data,
|
||||||
|
metadata,
|
||||||
|
privateMetadata
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
@ -72,108 +94,118 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
||||||
description: maybe(() => JSON.parse(collection.descriptionJson)),
|
description: maybe(() => JSON.parse(collection.descriptionJson)),
|
||||||
isFeatured,
|
isFeatured,
|
||||||
isPublished: maybe(() => collection.isPublished, false),
|
isPublished: maybe(() => collection.isPublished, false),
|
||||||
|
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: maybe(() => collection.name, ""),
|
name: maybe(() => collection.name, ""),
|
||||||
|
privateMetadata: collection?.privateMetadata?.map(
|
||||||
|
mapMetadataItemToInput
|
||||||
|
),
|
||||||
publicationDate: maybe(() => collection.publicationDate, ""),
|
publicationDate: maybe(() => collection.publicationDate, ""),
|
||||||
seoDescription: maybe(() => collection.seoDescription, ""),
|
seoDescription: maybe(() => collection.seoDescription, ""),
|
||||||
seoTitle: maybe(() => collection.seoTitle, "")
|
seoTitle: maybe(() => collection.seoTitle, "")
|
||||||
}}
|
}}
|
||||||
onSubmit={onSubmit}
|
onSubmit={handleSubmit}
|
||||||
confirmLeave
|
confirmLeave
|
||||||
>
|
>
|
||||||
{({ change, data, hasChanged, submit }) => (
|
{({ change, data, hasChanged, submit }) => {
|
||||||
<Container>
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
<AppHeader onBack={onBack}>
|
|
||||||
{intl.formatMessage(sectionNames.collections)}
|
return (
|
||||||
</AppHeader>
|
<Container>
|
||||||
<PageHeader title={maybe(() => collection.name)} />
|
<AppHeader onBack={onBack}>
|
||||||
<Grid>
|
{intl.formatMessage(sectionNames.collections)}
|
||||||
<div>
|
</AppHeader>
|
||||||
<CollectionDetails
|
<PageHeader title={maybe(() => collection.name)} />
|
||||||
collection={collection}
|
<Grid>
|
||||||
data={data}
|
|
||||||
disabled={disabled}
|
|
||||||
errors={errors}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
<CardSpacer />
|
|
||||||
<CollectionImage
|
|
||||||
data={data}
|
|
||||||
image={maybe(() => collection.backgroundImage)}
|
|
||||||
onImageDelete={onImageDelete}
|
|
||||||
onImageUpload={onImageUpload}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
<CardSpacer />
|
|
||||||
<CollectionProducts
|
|
||||||
disabled={disabled}
|
|
||||||
collection={collection}
|
|
||||||
{...collectionProductsProps}
|
|
||||||
/>
|
|
||||||
<CardSpacer />
|
|
||||||
<SeoForm
|
|
||||||
description={data.seoDescription}
|
|
||||||
disabled={disabled}
|
|
||||||
descriptionPlaceholder=""
|
|
||||||
helperText={intl.formatMessage({
|
|
||||||
defaultMessage:
|
|
||||||
"Add search engine title and description to make this collection easier to find"
|
|
||||||
})}
|
|
||||||
title={data.seoTitle}
|
|
||||||
titlePlaceholder={maybe(() => collection.name)}
|
|
||||||
onChange={change}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div>
|
<div>
|
||||||
<VisibilityCard
|
<CollectionDetails
|
||||||
|
collection={collection}
|
||||||
data={data}
|
data={data}
|
||||||
errors={errors}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
hiddenMessage={intl.formatMessage(
|
errors={errors}
|
||||||
{
|
|
||||||
defaultMessage: "will be visible from {date}",
|
|
||||||
description: "collection"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: localizeDate(data.publicationDate)
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
onChange={change}
|
onChange={change}
|
||||||
visibleMessage={intl.formatMessage(
|
/>
|
||||||
{
|
<CardSpacer />
|
||||||
defaultMessage: "since {date}",
|
<CollectionImage
|
||||||
description: "collection"
|
data={data}
|
||||||
},
|
image={maybe(() => collection.backgroundImage)}
|
||||||
{
|
onImageDelete={onImageDelete}
|
||||||
date: localizeDate(data.publicationDate)
|
onImageUpload={onImageUpload}
|
||||||
}
|
onChange={change}
|
||||||
)}
|
/>
|
||||||
>
|
<CardSpacer />
|
||||||
<FormSpacer />
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
<Hr />
|
<CardSpacer />
|
||||||
<ControlledCheckbox
|
<CollectionProducts
|
||||||
name={"isFeatured" as keyof CollectionDetailsPageFormData}
|
disabled={disabled}
|
||||||
label={intl.formatMessage({
|
collection={collection}
|
||||||
defaultMessage: "Feature on Homepage",
|
{...collectionProductsProps}
|
||||||
description: "switch button"
|
/>
|
||||||
})}
|
<CardSpacer />
|
||||||
checked={data.isFeatured}
|
<SeoForm
|
||||||
onChange={change}
|
description={data.seoDescription}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
descriptionPlaceholder=""
|
||||||
</VisibilityCard>
|
helperText={intl.formatMessage({
|
||||||
|
defaultMessage:
|
||||||
|
"Add search engine title and description to make this collection easier to find"
|
||||||
|
})}
|
||||||
|
title={data.seoTitle}
|
||||||
|
titlePlaceholder={maybe(() => collection.name)}
|
||||||
|
onChange={change}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
</Grid>
|
<div>
|
||||||
<SaveButtonBar
|
<VisibilityCard
|
||||||
state={saveButtonBarState}
|
data={data}
|
||||||
disabled={disabled || !hasChanged}
|
errors={errors}
|
||||||
onCancel={onBack}
|
disabled={disabled}
|
||||||
onDelete={onCollectionRemove}
|
hiddenMessage={intl.formatMessage(
|
||||||
onSave={submit}
|
{
|
||||||
/>
|
defaultMessage: "will be visible from {date}",
|
||||||
</Container>
|
description: "collection"
|
||||||
)}
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(data.publicationDate)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onChange={change}
|
||||||
|
visibleMessage={intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "since {date}",
|
||||||
|
description: "collection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: localizeDate(data.publicationDate)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FormSpacer />
|
||||||
|
<Hr />
|
||||||
|
<ControlledCheckbox
|
||||||
|
name={"isFeatured" as keyof CollectionDetailsPageFormData}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Feature on Homepage",
|
||||||
|
description: "switch button"
|
||||||
|
})}
|
||||||
|
checked={data.isFeatured}
|
||||||
|
onChange={change}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</VisibilityCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<SaveButtonBar
|
||||||
|
state={saveButtonBarState}
|
||||||
|
disabled={disabled || !hasChanged}
|
||||||
|
onCancel={onBack}
|
||||||
|
onDelete={onCollectionRemove}
|
||||||
|
onSave={submit}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { getMutationProviderData } from "../../misc";
|
|
||||||
import { PartialMutationProviderOutput } from "../../types";
|
|
||||||
import {
|
|
||||||
TypedCollectionAssignProductMutation,
|
|
||||||
TypedCollectionRemoveMutation,
|
|
||||||
TypedCollectionUpdateMutation,
|
|
||||||
TypedCollectionUpdateWithHomepageMutation,
|
|
||||||
TypedUnassignCollectionProductMutation
|
|
||||||
} from "../mutations";
|
|
||||||
import {
|
|
||||||
CollectionAssignProduct,
|
|
||||||
CollectionAssignProductVariables
|
|
||||||
} from "../types/CollectionAssignProduct";
|
|
||||||
import {
|
|
||||||
CollectionUpdate,
|
|
||||||
CollectionUpdateVariables
|
|
||||||
} from "../types/CollectionUpdate";
|
|
||||||
import {
|
|
||||||
CollectionUpdateWithHomepage,
|
|
||||||
CollectionUpdateWithHomepageVariables
|
|
||||||
} from "../types/CollectionUpdateWithHomepage";
|
|
||||||
import {
|
|
||||||
RemoveCollection,
|
|
||||||
RemoveCollectionVariables
|
|
||||||
} from "../types/RemoveCollection";
|
|
||||||
import {
|
|
||||||
UnassignCollectionProduct,
|
|
||||||
UnassignCollectionProductVariables
|
|
||||||
} from "../types/UnassignCollectionProduct";
|
|
||||||
|
|
||||||
interface CollectionUpdateOperationsProps {
|
|
||||||
children: (props: {
|
|
||||||
updateCollectionWithHomepage: PartialMutationProviderOutput<
|
|
||||||
CollectionUpdateWithHomepage,
|
|
||||||
CollectionUpdateWithHomepageVariables
|
|
||||||
>;
|
|
||||||
assignProduct: PartialMutationProviderOutput<
|
|
||||||
CollectionAssignProduct,
|
|
||||||
CollectionAssignProductVariables
|
|
||||||
>;
|
|
||||||
unassignProduct: PartialMutationProviderOutput<
|
|
||||||
UnassignCollectionProduct,
|
|
||||||
UnassignCollectionProductVariables
|
|
||||||
>;
|
|
||||||
updateCollection: PartialMutationProviderOutput<
|
|
||||||
CollectionUpdate,
|
|
||||||
CollectionUpdateVariables
|
|
||||||
>;
|
|
||||||
removeCollection: PartialMutationProviderOutput<
|
|
||||||
RemoveCollection,
|
|
||||||
RemoveCollectionVariables
|
|
||||||
>;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
onUpdate: (data: CollectionUpdate) => void;
|
|
||||||
onUpdateWithCollection: (data: CollectionUpdateWithHomepage) => void;
|
|
||||||
onProductAssign: (data: CollectionAssignProduct) => void;
|
|
||||||
onProductUnassign: (data: UnassignCollectionProduct) => void;
|
|
||||||
onRemove: (data: RemoveCollection) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CollectionOperations: React.FC<CollectionUpdateOperationsProps> = ({
|
|
||||||
children,
|
|
||||||
onUpdate,
|
|
||||||
onUpdateWithCollection,
|
|
||||||
onProductAssign,
|
|
||||||
onProductUnassign,
|
|
||||||
onRemove
|
|
||||||
}) => (
|
|
||||||
<TypedCollectionUpdateMutation onCompleted={onUpdate}>
|
|
||||||
{(...updateCollection) => (
|
|
||||||
<TypedCollectionRemoveMutation onCompleted={onRemove}>
|
|
||||||
{(...removeCollection) => (
|
|
||||||
<TypedCollectionAssignProductMutation onCompleted={onProductAssign}>
|
|
||||||
{(...assignProduct) => (
|
|
||||||
<TypedCollectionUpdateWithHomepageMutation
|
|
||||||
onCompleted={onUpdateWithCollection}
|
|
||||||
>
|
|
||||||
{(...updateWithHomepage) => (
|
|
||||||
<TypedUnassignCollectionProductMutation
|
|
||||||
onCompleted={onProductUnassign}
|
|
||||||
>
|
|
||||||
{(...unassignProduct) =>
|
|
||||||
children({
|
|
||||||
assignProduct: getMutationProviderData(
|
|
||||||
...assignProduct
|
|
||||||
),
|
|
||||||
removeCollection: getMutationProviderData(
|
|
||||||
...removeCollection
|
|
||||||
),
|
|
||||||
unassignProduct: getMutationProviderData(
|
|
||||||
...unassignProduct
|
|
||||||
),
|
|
||||||
updateCollection: getMutationProviderData(
|
|
||||||
...updateCollection
|
|
||||||
),
|
|
||||||
updateCollectionWithHomepage: getMutationProviderData(
|
|
||||||
...updateWithHomepage
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TypedUnassignCollectionProductMutation>
|
|
||||||
)}
|
|
||||||
</TypedCollectionUpdateWithHomepageMutation>
|
|
||||||
)}
|
|
||||||
</TypedCollectionAssignProductMutation>
|
|
||||||
)}
|
|
||||||
</TypedCollectionRemoveMutation>
|
|
||||||
)}
|
|
||||||
</TypedCollectionUpdateMutation>
|
|
||||||
);
|
|
||||||
export default CollectionOperations;
|
|
|
@ -80,7 +80,15 @@ export const collection: (
|
||||||
descriptionJson: JSON.stringify(content),
|
descriptionJson: JSON.stringify(content),
|
||||||
id: "Q29sbGVjdGlvbjox",
|
id: "Q29sbGVjdGlvbjox",
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "Summer collection",
|
name: "Summer collection",
|
||||||
|
privateMetadata: [],
|
||||||
products: {
|
products: {
|
||||||
__typename: "ProductCountableConnection",
|
__typename: "ProductCountableConnection",
|
||||||
edges: [
|
edges: [
|
||||||
|
|
|
@ -6,9 +6,9 @@ import {
|
||||||
productErrorFragment,
|
productErrorFragment,
|
||||||
shopErrorFragment
|
shopErrorFragment
|
||||||
} from "@saleor/fragments/errors";
|
} from "@saleor/fragments/errors";
|
||||||
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedMutation } from "../mutations";
|
|
||||||
import {
|
import {
|
||||||
CollectionAssignProduct,
|
CollectionAssignProduct,
|
||||||
CollectionAssignProductVariables
|
CollectionAssignProductVariables
|
||||||
|
@ -56,7 +56,7 @@ const collectionUpdate = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionUpdateMutation = TypedMutation<
|
export const useCollectionUpdateMutation = makeMutation<
|
||||||
CollectionUpdate,
|
CollectionUpdate,
|
||||||
CollectionUpdateVariables
|
CollectionUpdateVariables
|
||||||
>(collectionUpdate);
|
>(collectionUpdate);
|
||||||
|
@ -90,7 +90,7 @@ const collectionUpdateWithHomepage = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionUpdateWithHomepageMutation = TypedMutation<
|
export const useCollectionUpdateWithHomepageMutation = makeMutation<
|
||||||
CollectionUpdateWithHomepage,
|
CollectionUpdateWithHomepage,
|
||||||
CollectionUpdateWithHomepageVariables
|
CollectionUpdateWithHomepageVariables
|
||||||
>(collectionUpdateWithHomepage);
|
>(collectionUpdateWithHomepage);
|
||||||
|
@ -129,7 +129,7 @@ const assignCollectionProduct = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionAssignProductMutation = TypedMutation<
|
export const useCollectionAssignProductMutation = makeMutation<
|
||||||
CollectionAssignProduct,
|
CollectionAssignProduct,
|
||||||
CollectionAssignProductVariables
|
CollectionAssignProductVariables
|
||||||
>(assignCollectionProduct);
|
>(assignCollectionProduct);
|
||||||
|
@ -148,7 +148,7 @@ const createCollection = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionCreateMutation = TypedMutation<
|
export const useCollectionCreateMutation = makeMutation<
|
||||||
CreateCollection,
|
CreateCollection,
|
||||||
CreateCollectionVariables
|
CreateCollectionVariables
|
||||||
>(createCollection);
|
>(createCollection);
|
||||||
|
@ -163,7 +163,7 @@ const removeCollection = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionRemoveMutation = TypedMutation<
|
export const useCollectionRemoveMutation = makeMutation<
|
||||||
RemoveCollection,
|
RemoveCollection,
|
||||||
RemoveCollectionVariables
|
RemoveCollectionVariables
|
||||||
>(removeCollection);
|
>(removeCollection);
|
||||||
|
@ -213,7 +213,7 @@ const unassignCollectionProduct = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedUnassignCollectionProductMutation = TypedMutation<
|
export const useUnassignCollectionProductMutation = makeMutation<
|
||||||
UnassignCollectionProduct,
|
UnassignCollectionProduct,
|
||||||
UnassignCollectionProductVariables
|
UnassignCollectionProductVariables
|
||||||
>(unassignCollectionProduct);
|
>(unassignCollectionProduct);
|
||||||
|
@ -228,7 +228,7 @@ const collectionBulkDelete = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionBulkDelete = TypedMutation<
|
export const useCollectionBulkDelete = makeMutation<
|
||||||
CollectionBulkDelete,
|
CollectionBulkDelete,
|
||||||
CollectionBulkDeleteVariables
|
CollectionBulkDeleteVariables
|
||||||
>(collectionBulkDelete);
|
>(collectionBulkDelete);
|
||||||
|
@ -243,7 +243,7 @@ const collectionBulkPublish = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedCollectionBulkPublish = TypedMutation<
|
export const useCollectionBulkPublish = makeMutation<
|
||||||
CollectionBulkPublish,
|
CollectionBulkPublish,
|
||||||
CollectionBulkPublishVariables
|
CollectionBulkPublishVariables
|
||||||
>(collectionBulkPublish);
|
>(collectionBulkPublish);
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
// GraphQL query operation: CollectionDetails
|
// GraphQL query operation: CollectionDetails
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CollectionDetails_collection_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionDetails_collection_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollectionDetails_collection_backgroundImage {
|
export interface CollectionDetails_collection_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -56,6 +68,8 @@ export interface CollectionDetails_collection {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
metadata: (CollectionDetails_collection_metadata | null)[];
|
||||||
|
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { CollectionInput, ProductErrorCode } from "./../../types/globalTypes";
|
||||||
// GraphQL mutation operation: CollectionUpdate
|
// GraphQL mutation operation: CollectionUpdate
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CollectionUpdate_collectionUpdate_collection_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionUpdate_collectionUpdate_collection_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollectionUpdate_collectionUpdate_collection_backgroundImage {
|
export interface CollectionUpdate_collectionUpdate_collection_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -19,6 +31,8 @@ export interface CollectionUpdate_collectionUpdate_collection {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
||||||
|
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
|
|
|
@ -30,6 +30,18 @@ export interface CollectionUpdateWithHomepage_homepageCollectionUpdate {
|
||||||
shop: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop | null;
|
shop: CollectionUpdateWithHomepage_homepageCollectionUpdate_shop | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage {
|
export interface CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -41,6 +53,8 @@ export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
||||||
|
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { CollectionCreateInput, ProductErrorCode } from "./../../types/globalTyp
|
||||||
// GraphQL mutation operation: CreateCollection
|
// GraphQL mutation operation: CreateCollection
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CreateCollection_collectionCreate_collection_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCollection_collectionCreate_collection_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateCollection_collectionCreate_collection_backgroundImage {
|
export interface CreateCollection_collectionCreate_collection_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -19,6 +31,8 @@ export interface CreateCollection_collectionCreate_collection {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
||||||
|
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
|
|
|
@ -7,8 +7,7 @@ import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { CollectionCreateInput } from "../../types/globalTypes";
|
import { CollectionCreateInput } from "../../types/globalTypes";
|
||||||
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
||||||
import { TypedCollectionCreateMutation } from "../mutations";
|
import { useCollectionCreateMutation } from "../mutations";
|
||||||
import { CreateCollection } from "../types/CreateCollection";
|
|
||||||
import { collectionListUrl, collectionUrl } from "../urls";
|
import { collectionListUrl, collectionUrl } from "../urls";
|
||||||
|
|
||||||
export const CollectionCreate: React.FC = () => {
|
export const CollectionCreate: React.FC = () => {
|
||||||
|
@ -16,62 +15,61 @@ export const CollectionCreate: React.FC = () => {
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleCollectionCreateSuccess = (data: CreateCollection) => {
|
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
||||||
if (data.collectionCreate.errors.length === 0) {
|
onCompleted: data => {
|
||||||
notify({
|
if (data.collectionCreate.errors.length === 0) {
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
navigate(collectionUrl(data.collectionCreate.collection.id));
|
|
||||||
} else {
|
|
||||||
const backgroundImageError = data.collectionCreate.errors.find(
|
|
||||||
error =>
|
|
||||||
error.field === ("backgroundImage" as keyof CollectionCreateInput)
|
|
||||||
);
|
|
||||||
if (backgroundImageError) {
|
|
||||||
notify({
|
notify({
|
||||||
status: "error",
|
status: "success",
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
});
|
});
|
||||||
|
navigate(collectionUrl(data.collectionCreate.collection.id));
|
||||||
|
} else {
|
||||||
|
const backgroundImageError = data.collectionCreate.errors.find(
|
||||||
|
error =>
|
||||||
|
error.field === ("backgroundImage" as keyof CollectionCreateInput)
|
||||||
|
);
|
||||||
|
if (backgroundImageError) {
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedCollectionCreateMutation onCompleted={handleCollectionCreateSuccess}>
|
<>
|
||||||
{(createCollection, createCollectionOpts) => (
|
<WindowTitle
|
||||||
<>
|
title={intl.formatMessage({
|
||||||
<WindowTitle
|
defaultMessage: "Create collection",
|
||||||
title={intl.formatMessage({
|
description: "window title"
|
||||||
defaultMessage: "Create collection",
|
})}
|
||||||
description: "window title"
|
/>
|
||||||
})}
|
<CollectionCreatePage
|
||||||
/>
|
errors={createCollectionOpts.data?.collectionCreate.errors || []}
|
||||||
<CollectionCreatePage
|
onBack={() => navigate(collectionListUrl())}
|
||||||
errors={createCollectionOpts.data?.collectionCreate.errors || []}
|
disabled={createCollectionOpts.loading}
|
||||||
onBack={() => navigate(collectionListUrl())}
|
onSubmit={formData =>
|
||||||
disabled={createCollectionOpts.loading}
|
createCollection({
|
||||||
onSubmit={formData =>
|
variables: {
|
||||||
createCollection({
|
input: {
|
||||||
variables: {
|
backgroundImage: formData.backgroundImage.value,
|
||||||
input: {
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
backgroundImage: formData.backgroundImage.value,
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
isPublished: formData.isPublished,
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
name: formData.name,
|
||||||
isPublished: formData.isPublished,
|
seo: {
|
||||||
name: formData.name,
|
description: formData.seoDescription,
|
||||||
seo: {
|
title: formData.seoTitle
|
||||||
description: formData.seoDescription,
|
|
||||||
title: formData.seoTitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
saveButtonBarState={createCollectionOpts.status}
|
})
|
||||||
/>
|
}
|
||||||
</>
|
saveButtonBarState={createCollectionOpts.status}
|
||||||
)}
|
/>
|
||||||
</TypedCollectionCreateMutation>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CollectionCreate;
|
export default CollectionCreate;
|
||||||
|
|
|
@ -14,6 +14,11 @@ import usePaginator, {
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -23,13 +28,15 @@ import { CollectionInput } from "../../types/globalTypes";
|
||||||
import CollectionDetailsPage, {
|
import CollectionDetailsPage, {
|
||||||
CollectionDetailsPageFormData
|
CollectionDetailsPageFormData
|
||||||
} from "../components/CollectionDetailsPage/CollectionDetailsPage";
|
} from "../components/CollectionDetailsPage/CollectionDetailsPage";
|
||||||
import CollectionOperations from "../containers/CollectionOperations";
|
import {
|
||||||
|
useCollectionAssignProductMutation,
|
||||||
|
useCollectionRemoveMutation,
|
||||||
|
useCollectionUpdateMutation,
|
||||||
|
useCollectionUpdateWithHomepageMutation,
|
||||||
|
useUnassignCollectionProductMutation
|
||||||
|
} from "../mutations";
|
||||||
import { TypedCollectionDetailsQuery } from "../queries";
|
import { TypedCollectionDetailsQuery } from "../queries";
|
||||||
import { CollectionAssignProduct } from "../types/CollectionAssignProduct";
|
|
||||||
import { CollectionUpdate } from "../types/CollectionUpdate";
|
import { CollectionUpdate } from "../types/CollectionUpdate";
|
||||||
import { CollectionUpdateWithHomepage } from "../types/CollectionUpdateWithHomepage";
|
|
||||||
import { RemoveCollection } from "../types/RemoveCollection";
|
|
||||||
import { UnassignCollectionProduct } from "../types/UnassignCollectionProduct";
|
|
||||||
import {
|
import {
|
||||||
collectionListUrl,
|
collectionListUrl,
|
||||||
collectionUrl,
|
collectionUrl,
|
||||||
|
@ -56,6 +63,90 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
const { search, result } = useProductSearch({
|
const { search, result } = useProductSearch({
|
||||||
variables: DEFAULT_INITIAL_SEARCH_DATA
|
variables: DEFAULT_INITIAL_SEARCH_DATA
|
||||||
});
|
});
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
|
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
||||||
|
if (data.collectionUpdate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
navigate(collectionUrl(id));
|
||||||
|
} else {
|
||||||
|
const backgroundImageError = data.collectionUpdate.errors.find(
|
||||||
|
error => error.field === ("backgroundImage" as keyof CollectionInput)
|
||||||
|
);
|
||||||
|
if (backgroundImageError) {
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [updateCollection, updateCollectionOpts] = useCollectionUpdateMutation({
|
||||||
|
onCompleted: handleCollectionUpdate
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
updateCollectionWithHomepage,
|
||||||
|
updateCollectionWithHomepageOpts
|
||||||
|
] = useCollectionUpdateWithHomepageMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.homepageCollectionUpdate.errors.length === 0) {
|
||||||
|
handleCollectionUpdate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [assignProduct, assignProductOpts] = useCollectionAssignProductMutation(
|
||||||
|
{
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.collectionAddProducts.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Added product to collection"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(collectionUrl(id), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
unassignProduct,
|
||||||
|
unassignProductOpts
|
||||||
|
] = useUnassignCollectionProductMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.collectionRemoveProducts.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Deleted product from collection"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [removeCollection, removeCollectionOpts] = useCollectionRemoveMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.collectionDelete.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Deleted collection"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(collectionListUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
CollectionUrlDialog,
|
CollectionUrlDialog,
|
||||||
|
@ -76,298 +167,236 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
if (collection === null) {
|
if (collection === null) {
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
return <NotFoundPage onBack={handleBack} />;
|
||||||
}
|
}
|
||||||
const handleCollectionUpdate = (data: CollectionUpdate) => {
|
|
||||||
if (data.collectionUpdate.errors.length === 0) {
|
const handleUpdate = async (
|
||||||
notify({
|
formData: CollectionDetailsPageFormData
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
navigate(collectionUrl(id));
|
|
||||||
} else {
|
|
||||||
const backgroundImageError = data.collectionUpdate.errors.find(
|
|
||||||
error =>
|
|
||||||
error.field === ("backgroundImage" as keyof CollectionInput)
|
|
||||||
);
|
|
||||||
if (backgroundImageError) {
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleCollectioUpdateWithHomepage = (
|
|
||||||
data: CollectionUpdateWithHomepage
|
|
||||||
) => {
|
) => {
|
||||||
if (data.homepageCollectionUpdate.errors.length === 0) {
|
const input: CollectionInput = {
|
||||||
handleCollectionUpdate(data);
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
}
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
};
|
isPublished: formData.isPublished,
|
||||||
|
name: formData.name,
|
||||||
|
publicationDate: formData.publicationDate,
|
||||||
|
seo: {
|
||||||
|
description: formData.seoDescription,
|
||||||
|
title: formData.seoTitle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const isFeatured = data.shop.homepageCollection
|
||||||
|
? data.shop.homepageCollection.id === data.collection.id
|
||||||
|
: false;
|
||||||
|
|
||||||
const handleProductAssign = (data: CollectionAssignProduct) => {
|
if (formData.isFeatured !== isFeatured) {
|
||||||
if (data.collectionAddProducts.errors.length === 0) {
|
const result = await updateCollectionWithHomepage({
|
||||||
notify({
|
variables: {
|
||||||
status: "success",
|
homepageId: formData.isFeatured ? id : null,
|
||||||
text: intl.formatMessage({
|
id,
|
||||||
defaultMessage: "Added product to collection"
|
input
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
return [
|
||||||
|
...result.data.collectionUpdate.errors,
|
||||||
|
...result.data.homepageCollectionUpdate.errors
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
const result = await updateCollection({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input
|
||||||
|
}
|
||||||
});
|
});
|
||||||
navigate(collectionUrl(id), true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleProductUnassign = (data: UnassignCollectionProduct) => {
|
return result.data.collectionUpdate.errors;
|
||||||
if (data.collectionRemoveProducts.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Deleted product from collection"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
reset();
|
|
||||||
closeModal();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
data?.collection,
|
||||||
|
handleUpdate,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
|
const formTransitionState = getMutationState(
|
||||||
|
updateCollectionOpts.called ||
|
||||||
|
updateCollectionWithHomepageOpts.called,
|
||||||
|
updateCollectionOpts.loading ||
|
||||||
|
updateCollectionWithHomepageOpts.loading,
|
||||||
|
updateCollectionOpts.data?.collectionUpdate.errors,
|
||||||
|
updateCollectionWithHomepageOpts.data?.collectionUpdate.errors,
|
||||||
|
updateCollectionWithHomepageOpts.data?.homepageCollectionUpdate.errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
|
data?.collection?.products?.pageInfo,
|
||||||
|
paginationState,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
const handleCollectionRemove = (data: RemoveCollection) => {
|
|
||||||
if (data.collectionDelete.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Deleted collection"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
navigate(collectionListUrl());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<CollectionOperations
|
<>
|
||||||
onUpdate={handleCollectionUpdate}
|
<WindowTitle title={maybe(() => data.collection.name)} />
|
||||||
onUpdateWithCollection={handleCollectioUpdateWithHomepage}
|
<CollectionDetailsPage
|
||||||
onProductAssign={handleProductAssign}
|
onAdd={() => openModal("assign")}
|
||||||
onProductUnassign={handleProductUnassign}
|
onBack={handleBack}
|
||||||
onRemove={handleCollectionRemove}
|
disabled={loading}
|
||||||
>
|
collection={data?.collection}
|
||||||
{({
|
errors={updateCollectionOpts?.data?.collectionUpdate.errors || []}
|
||||||
updateCollection,
|
isFeatured={maybe(
|
||||||
updateCollectionWithHomepage,
|
() => data.shop.homepageCollection.id === data.collection.id,
|
||||||
assignProduct,
|
false
|
||||||
unassignProduct,
|
)}
|
||||||
removeCollection
|
onCollectionRemove={() => openModal("remove")}
|
||||||
}) => {
|
onImageDelete={() => openModal("removeImage")}
|
||||||
const handleSubmit = (
|
onImageUpload={file =>
|
||||||
formData: CollectionDetailsPageFormData
|
updateCollection({
|
||||||
) => {
|
variables: {
|
||||||
const input: CollectionInput = {
|
id,
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
input: {
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
backgroundImage: file
|
||||||
isPublished: formData.isPublished,
|
}
|
||||||
name: formData.name,
|
|
||||||
publicationDate: formData.publicationDate,
|
|
||||||
seo: {
|
|
||||||
description: formData.seoDescription,
|
|
||||||
title: formData.seoTitle
|
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
const isFeatured = data.shop.homepageCollection
|
}
|
||||||
? data.shop.homepageCollection.id === data.collection.id
|
onSubmit={handleSubmit}
|
||||||
: false;
|
onNextPage={loadNextPage}
|
||||||
|
onPreviousPage={loadPreviousPage}
|
||||||
if (formData.isFeatured !== isFeatured) {
|
pageInfo={pageInfo}
|
||||||
updateCollectionWithHomepage.mutate({
|
onProductUnassign={(productId, event) => {
|
||||||
homepageId: formData.isFeatured ? id : null,
|
event.stopPropagation();
|
||||||
id,
|
unassignProduct({
|
||||||
input
|
variables: {
|
||||||
});
|
collectionId: id,
|
||||||
} else {
|
productIds: [productId],
|
||||||
updateCollection.mutate({
|
...paginationState
|
||||||
id,
|
}
|
||||||
input
|
});
|
||||||
});
|
}}
|
||||||
}
|
onRowClick={id => () => navigate(productUrl(id))}
|
||||||
};
|
saveButtonBarState={formTransitionState}
|
||||||
|
toolbar={
|
||||||
const formTransitionState = getMutationState(
|
<Button
|
||||||
updateCollection.opts.called ||
|
color="primary"
|
||||||
updateCollectionWithHomepage.opts.called,
|
onClick={() =>
|
||||||
updateCollection.opts.loading ||
|
openModal("unassign", {
|
||||||
updateCollectionWithHomepage.opts.loading,
|
ids: listElements
|
||||||
maybe(() => updateCollection.opts.data.collectionUpdate.errors),
|
})
|
||||||
maybe(
|
}
|
||||||
() =>
|
>
|
||||||
updateCollectionWithHomepage.opts.data.collectionUpdate
|
<FormattedMessage
|
||||||
.errors
|
defaultMessage="Unassign"
|
||||||
),
|
description="unassign product from collection, button"
|
||||||
maybe(
|
|
||||||
() =>
|
|
||||||
updateCollectionWithHomepage.opts.data
|
|
||||||
.homepageCollectionUpdate.errors
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
|
||||||
maybe(() => data.collection.products.pageInfo),
|
|
||||||
paginationState,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WindowTitle title={maybe(() => data.collection.name)} />
|
|
||||||
<CollectionDetailsPage
|
|
||||||
onAdd={() => openModal("assign")}
|
|
||||||
onBack={handleBack}
|
|
||||||
disabled={loading}
|
|
||||||
collection={data?.collection}
|
|
||||||
errors={
|
|
||||||
updateCollection.opts?.data?.collectionUpdate.errors || []
|
|
||||||
}
|
|
||||||
isFeatured={maybe(
|
|
||||||
() =>
|
|
||||||
data.shop.homepageCollection.id === data.collection.id,
|
|
||||||
false
|
|
||||||
)}
|
|
||||||
onCollectionRemove={() => openModal("remove")}
|
|
||||||
onImageDelete={() => openModal("removeImage")}
|
|
||||||
onImageUpload={file =>
|
|
||||||
updateCollection.mutate({
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
backgroundImage: file
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onNextPage={loadNextPage}
|
|
||||||
onPreviousPage={loadPreviousPage}
|
|
||||||
pageInfo={pageInfo}
|
|
||||||
onProductUnassign={(productId, event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
unassignProduct.mutate({
|
|
||||||
collectionId: id,
|
|
||||||
productIds: [productId],
|
|
||||||
...paginationState
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onRowClick={id => () => navigate(productUrl(id))}
|
|
||||||
saveButtonBarState={formTransitionState}
|
|
||||||
toolbar={
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("unassign", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Unassign"
|
|
||||||
description="unassign product from collection, button"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
/>
|
/>
|
||||||
<AssignProductDialog
|
</Button>
|
||||||
confirmButtonState={assignProduct.opts.status}
|
}
|
||||||
open={params.action === "assign"}
|
isChecked={isSelected}
|
||||||
onFetch={search}
|
selected={listElements.length}
|
||||||
loading={result.loading}
|
toggle={toggle}
|
||||||
onClose={closeModal}
|
toggleAll={toggleAll}
|
||||||
onSubmit={products =>
|
/>
|
||||||
assignProduct.mutate({
|
<AssignProductDialog
|
||||||
...paginationState,
|
confirmButtonState={assignProductOpts.status}
|
||||||
collectionId: id,
|
open={params.action === "assign"}
|
||||||
productIds: products.map(product => product.id)
|
onFetch={search}
|
||||||
})
|
loading={result.loading}
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={products =>
|
||||||
|
assignProduct({
|
||||||
|
variables: {
|
||||||
|
...paginationState,
|
||||||
|
collectionId: id,
|
||||||
|
productIds: products.map(product => product.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
products={maybe(() =>
|
||||||
|
result.data.search.edges
|
||||||
|
.map(edge => edge.node)
|
||||||
|
.filter(suggestedProduct => suggestedProduct.id)
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<ActionDialog
|
||||||
|
confirmButtonState={removeCollectionOpts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() =>
|
||||||
|
removeCollection({
|
||||||
|
variables: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
open={params.action === "remove"}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete Collection",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
variant="delete"
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Are you sure you want to delete {collectionName}?"
|
||||||
|
values={{
|
||||||
|
collectionName: (
|
||||||
|
<strong>
|
||||||
|
{maybe(() => data.collection.name, "...")}
|
||||||
|
</strong>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
confirmButtonState={unassignProductOpts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() =>
|
||||||
|
unassignProduct({
|
||||||
|
variables: {
|
||||||
|
...paginationState,
|
||||||
|
collectionId: id,
|
||||||
|
productIds: params.ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
open={params.action === "unassign"}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Unassign products from collection",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to unassign this product?} other{Are you sure you want to unassign {displayQuantity} products?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: (
|
||||||
|
<strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
confirmButtonState={updateCollectionOpts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() =>
|
||||||
|
updateCollection({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
backgroundImage: null
|
||||||
}
|
}
|
||||||
products={maybe(() =>
|
}
|
||||||
result.data.search.edges
|
})
|
||||||
.map(edge => edge.node)
|
}
|
||||||
.filter(suggestedProduct => suggestedProduct.id)
|
open={params.action === "removeImage"}
|
||||||
)}
|
title={intl.formatMessage({
|
||||||
/>
|
defaultMessage: "Delete image",
|
||||||
<ActionDialog
|
description: "dialog title"
|
||||||
confirmButtonState={removeCollection.opts.status}
|
})}
|
||||||
onClose={closeModal}
|
variant="delete"
|
||||||
onConfirm={() => removeCollection.mutate({ id })}
|
>
|
||||||
open={params.action === "remove"}
|
<DialogContentText>
|
||||||
title={intl.formatMessage({
|
<FormattedMessage defaultMessage="Are you sure you want to delete collection's image?" />
|
||||||
defaultMessage: "Delete Collection",
|
</DialogContentText>
|
||||||
description: "dialog title"
|
</ActionDialog>
|
||||||
})}
|
</>
|
||||||
variant="delete"
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Are you sure you want to delete {collectionName}?"
|
|
||||||
values={{
|
|
||||||
collectionName: (
|
|
||||||
<strong>
|
|
||||||
{maybe(() => data.collection.name, "...")}
|
|
||||||
</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
confirmButtonState={unassignProduct.opts.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
unassignProduct.mutate({
|
|
||||||
...paginationState,
|
|
||||||
collectionId: id,
|
|
||||||
productIds: params.ids
|
|
||||||
})
|
|
||||||
}
|
|
||||||
open={params.action === "unassign"}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Unassign products from collection",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to unassign this product?} other{Are you sure you want to unassign {displayQuantity} products?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
confirmButtonState={updateCollection.opts.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
updateCollection.mutate({
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
backgroundImage: null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
open={params.action === "removeImage"}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete image",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
variant="delete"
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage defaultMessage="Are you sure you want to delete collection's image?" />
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</CollectionOperations>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</TypedCollectionDetailsQuery>
|
</TypedCollectionDetailsQuery>
|
||||||
|
|
|
@ -27,12 +27,10 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
||||||
import {
|
import {
|
||||||
TypedCollectionBulkDelete,
|
useCollectionBulkDelete,
|
||||||
TypedCollectionBulkPublish
|
useCollectionBulkPublish
|
||||||
} from "../../mutations";
|
} from "../../mutations";
|
||||||
import { useCollectionListQuery } from "../../queries";
|
import { useCollectionListQuery } from "../../queries";
|
||||||
import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
|
|
||||||
import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
|
|
||||||
import {
|
import {
|
||||||
collectionAddUrl,
|
collectionAddUrl,
|
||||||
collectionListUrl,
|
collectionListUrl,
|
||||||
|
@ -83,6 +81,40 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
variables: queryVariables
|
variables: queryVariables
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
collectionBulkDelete,
|
||||||
|
collectionBulkDeleteOpts
|
||||||
|
] = useCollectionBulkDelete({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.collectionBulkDelete.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
reset();
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
collectionBulkPublish,
|
||||||
|
collectionBulkPublishOpts
|
||||||
|
] = useCollectionBulkPublish({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.collectionBulkPublish.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
reset();
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
const currentTab =
|
const currentTab =
|
||||||
|
@ -136,225 +168,178 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => {
|
|
||||||
if (data.collectionBulkDelete.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
refetch();
|
|
||||||
reset();
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
|
|
||||||
if (data.collectionBulkPublish.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
refetch();
|
|
||||||
reset();
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
const handleSort = createSortHandler(navigate, collectionListUrl, params);
|
||||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedCollectionBulkDelete onCompleted={handleCollectionBulkDelete}>
|
<>
|
||||||
{(collectionBulkDelete, collectionBulkDeleteOpts) => (
|
<CollectionListPage
|
||||||
<TypedCollectionBulkPublish onCompleted={handleCollectionBulkPublish}>
|
currencySymbol={currencySymbol}
|
||||||
{(collectionBulkPublish, collectionBulkPublishOpts) => (
|
currentTab={currentTab}
|
||||||
<>
|
filterOpts={getFilterOpts(params)}
|
||||||
<CollectionListPage
|
initialSearch={params.query || ""}
|
||||||
currencySymbol={currencySymbol}
|
onSearchChange={handleSearchChange}
|
||||||
currentTab={currentTab}
|
onFilterChange={changeFilters}
|
||||||
filterOpts={getFilterOpts(params)}
|
onAdd={() => navigate(collectionAddUrl)}
|
||||||
initialSearch={params.query || ""}
|
onAll={resetFilters}
|
||||||
onSearchChange={handleSearchChange}
|
onTabChange={handleTabChange}
|
||||||
onFilterChange={changeFilters}
|
onTabDelete={() => openModal("delete-search")}
|
||||||
onAdd={() => navigate(collectionAddUrl)}
|
onTabSave={() => openModal("save-search")}
|
||||||
onAll={resetFilters}
|
tabs={tabs.map(tab => tab.name)}
|
||||||
onTabChange={handleTabChange}
|
disabled={loading}
|
||||||
onTabDelete={() => openModal("delete-search")}
|
collections={maybe(() => data.collections.edges.map(edge => edge.node))}
|
||||||
onTabSave={() => openModal("save-search")}
|
settings={settings}
|
||||||
tabs={tabs.map(tab => tab.name)}
|
onNextPage={loadNextPage}
|
||||||
disabled={loading}
|
onPreviousPage={loadPreviousPage}
|
||||||
collections={maybe(() =>
|
onSort={handleSort}
|
||||||
data.collections.edges.map(edge => edge.node)
|
onUpdateListSettings={updateListSettings}
|
||||||
)}
|
pageInfo={pageInfo}
|
||||||
settings={settings}
|
sort={getSortParams(params)}
|
||||||
onNextPage={loadNextPage}
|
onRowClick={id => () => navigate(collectionUrl(id))}
|
||||||
onPreviousPage={loadPreviousPage}
|
toolbar={
|
||||||
onSort={handleSort}
|
<>
|
||||||
onUpdateListSettings={updateListSettings}
|
<Button
|
||||||
pageInfo={pageInfo}
|
color="primary"
|
||||||
sort={getSortParams(params)}
|
onClick={() =>
|
||||||
onRowClick={id => () => navigate(collectionUrl(id))}
|
openModal("unpublish", {
|
||||||
toolbar={
|
ids: listElements
|
||||||
<>
|
})
|
||||||
<Button
|
}
|
||||||
color="primary"
|
>
|
||||||
onClick={() =>
|
<FormattedMessage
|
||||||
openModal("unpublish", {
|
defaultMessage="Unpublish"
|
||||||
ids: listElements
|
description="unpublish collections"
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Unpublish"
|
|
||||||
description="unpublish collections"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("publish", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Publish"
|
|
||||||
description="publish collections"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("remove", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
</Button>
|
||||||
open={
|
<Button
|
||||||
params.action === "publish" &&
|
color="primary"
|
||||||
maybe(() => params.ids.length > 0)
|
onClick={() =>
|
||||||
}
|
openModal("publish", {
|
||||||
onClose={closeModal}
|
ids: listElements
|
||||||
confirmButtonState={collectionBulkPublishOpts.status}
|
})
|
||||||
onConfirm={() =>
|
}
|
||||||
collectionBulkPublish({
|
>
|
||||||
variables: {
|
<FormattedMessage
|
||||||
ids: params.ids,
|
defaultMessage="Publish"
|
||||||
isPublished: true
|
description="publish collections"
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="default"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Publish collections",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to publish this collection?} other{Are you sure you want to publish {displayQuantity} collections?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={
|
|
||||||
params.action === "unpublish" &&
|
|
||||||
maybe(() => params.ids.length > 0)
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
confirmButtonState={collectionBulkPublishOpts.status}
|
|
||||||
onConfirm={() =>
|
|
||||||
collectionBulkPublish({
|
|
||||||
variables: {
|
|
||||||
ids: params.ids,
|
|
||||||
isPublished: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="default"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Unpublish collections",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to unpublish this collection?} other{Are you sure you want to unpublish {displayQuantity} collections?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={
|
|
||||||
params.action === "remove" &&
|
|
||||||
maybe(() => params.ids.length > 0)
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
confirmButtonState={collectionBulkDeleteOpts.status}
|
|
||||||
onConfirm={() =>
|
|
||||||
collectionBulkDelete({
|
|
||||||
variables: {
|
|
||||||
ids: params.ids
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="delete"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete collections",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this collection?} other{Are you sure you want to delete {displayQuantity} collections?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<SaveFilterTabDialog
|
|
||||||
open={params.action === "save-search"}
|
|
||||||
confirmButtonState="default"
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleTabSave}
|
|
||||||
/>
|
/>
|
||||||
<DeleteFilterTabDialog
|
</Button>
|
||||||
open={params.action === "delete-search"}
|
<IconButton
|
||||||
confirmButtonState="default"
|
color="primary"
|
||||||
onClose={closeModal}
|
onClick={() =>
|
||||||
onSubmit={handleTabDelete}
|
openModal("remove", {
|
||||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
ids: listElements
|
||||||
/>
|
})
|
||||||
</>
|
}
|
||||||
)}
|
>
|
||||||
</TypedCollectionBulkPublish>
|
<DeleteIcon />
|
||||||
)}
|
</IconButton>
|
||||||
</TypedCollectionBulkDelete>
|
</>
|
||||||
|
}
|
||||||
|
isChecked={isSelected}
|
||||||
|
selected={listElements.length}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
/>
|
||||||
|
<ActionDialog
|
||||||
|
open={params.action === "publish" && maybe(() => params.ids.length > 0)}
|
||||||
|
onClose={closeModal}
|
||||||
|
confirmButtonState={collectionBulkPublishOpts.status}
|
||||||
|
onConfirm={() =>
|
||||||
|
collectionBulkPublish({
|
||||||
|
variables: {
|
||||||
|
ids: params.ids,
|
||||||
|
isPublished: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
variant="default"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Publish collections",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to publish this collection?} other{Are you sure you want to publish {displayQuantity} collections?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
open={
|
||||||
|
params.action === "unpublish" && maybe(() => params.ids.length > 0)
|
||||||
|
}
|
||||||
|
onClose={closeModal}
|
||||||
|
confirmButtonState={collectionBulkPublishOpts.status}
|
||||||
|
onConfirm={() =>
|
||||||
|
collectionBulkPublish({
|
||||||
|
variables: {
|
||||||
|
ids: params.ids,
|
||||||
|
isPublished: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
variant="default"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Unpublish collections",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to unpublish this collection?} other{Are you sure you want to unpublish {displayQuantity} collections?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||||
|
onClose={closeModal}
|
||||||
|
confirmButtonState={collectionBulkDeleteOpts.status}
|
||||||
|
onConfirm={() =>
|
||||||
|
collectionBulkDelete({
|
||||||
|
variables: {
|
||||||
|
ids: params.ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
variant="delete"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete collections",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this collection?} other{Are you sure you want to delete {displayQuantity} collections?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<SaveFilterTabDialog
|
||||||
|
open={params.action === "save-search"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleTabSave}
|
||||||
|
/>
|
||||||
|
<DeleteFilterTabDialog
|
||||||
|
open={params.action === "delete-search"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleTabDelete}
|
||||||
|
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CollectionList;
|
export default CollectionList;
|
||||||
|
|
19
src/components/Metadata/Metadata.stories.tsx
Normal file
19
src/components/Metadata/Metadata.stories.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import useForm from "@saleor/hooks/useForm";
|
||||||
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { props } from "./fixtures";
|
||||||
|
import Metadata from "./Metadata";
|
||||||
|
|
||||||
|
const InteractiveStory: React.FC = () => {
|
||||||
|
const { change, data } = useForm(props.data, () => undefined);
|
||||||
|
|
||||||
|
return <Metadata data={data} onChange={change} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Generics / Metadata", module)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("default", () => <Metadata {...props} />)
|
||||||
|
.add("loading", () => <Metadata {...props} data={undefined} />)
|
||||||
|
.add("interactive", () => <InteractiveStory />);
|
162
src/components/Metadata/Metadata.test.tsx
Normal file
162
src/components/Metadata/Metadata.test.tsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import useForm from "@saleor/hooks/useForm";
|
||||||
|
import Wrapper from "@test/wrapper";
|
||||||
|
import { configure } from "enzyme";
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import Adapter from "enzyme-adapter-react-16";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { props } from "./fixtures";
|
||||||
|
import Metadata from "./Metadata";
|
||||||
|
|
||||||
|
configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
|
const expandButton = 'data-test="expand"';
|
||||||
|
|
||||||
|
const Component: React.FC = () => {
|
||||||
|
const { change, data } = useForm(props.data, jest.fn());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<Metadata data={data} onChange={change} />
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Metadata editor", () => {
|
||||||
|
it("can expand field", () => {
|
||||||
|
const wrapper = mount(<Component />);
|
||||||
|
|
||||||
|
const expandDataEl = "data-test-expanded";
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandDataEl}]`)
|
||||||
|
.first()
|
||||||
|
.prop(expandDataEl)
|
||||||
|
).toEqual(false);
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandButton}]`)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandDataEl}]`)
|
||||||
|
.first()
|
||||||
|
.prop(expandDataEl)
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can edit field name", () => {
|
||||||
|
const wrapper = mount(<Component />);
|
||||||
|
|
||||||
|
const inputNameSelector = '[name="name:1"] input';
|
||||||
|
|
||||||
|
// Expand to reveal fields
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandButton}]`)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.prop("value")
|
||||||
|
).toEqual(props.data.metadata[1].key);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.simulate("change", { target: { name: "name:1", value: "x" } });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.prop("value")
|
||||||
|
).toEqual("x");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can edit field value", () => {
|
||||||
|
const wrapper = mount(<Component />);
|
||||||
|
|
||||||
|
const inputNameSelector = '[name="value:1"] textarea';
|
||||||
|
|
||||||
|
// Expand to reveal fields
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandButton}]`)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.prop("value")
|
||||||
|
).toEqual(props.data.metadata[1].value);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.simulate("change", { target: { name: "value:1", value: "x" } });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find(inputNameSelector)
|
||||||
|
.first()
|
||||||
|
.prop("value")
|
||||||
|
).toEqual("x");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can delete field", () => {
|
||||||
|
const wrapper = mount(<Component />);
|
||||||
|
|
||||||
|
const fieldSelector = 'tr[data-test="field"]';
|
||||||
|
const deleteButtonSelector = '[data-test="deleteField"]';
|
||||||
|
|
||||||
|
// Expand to reveal fields
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandButton}]`)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(wrapper.find(fieldSelector).length).toEqual(
|
||||||
|
props.data.metadata.length
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(deleteButtonSelector)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(wrapper.find(fieldSelector).length).toEqual(
|
||||||
|
props.data.metadata.length - 1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can add field", () => {
|
||||||
|
const wrapper = mount(<Component />);
|
||||||
|
|
||||||
|
const fieldSelector = 'tr[data-test="field"]';
|
||||||
|
const addButtonSelector = '[data-test="addField"]';
|
||||||
|
|
||||||
|
// Expand to reveal fields
|
||||||
|
wrapper
|
||||||
|
.find(`[${expandButton}]`)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(wrapper.find(fieldSelector).length).toEqual(
|
||||||
|
props.data.metadata.length
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find(addButtonSelector)
|
||||||
|
.first()
|
||||||
|
.simulate("click");
|
||||||
|
|
||||||
|
expect(wrapper.find(fieldSelector).length).toEqual(
|
||||||
|
props.data.metadata.length + 1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
73
src/components/Metadata/Metadata.tsx
Normal file
73
src/components/Metadata/Metadata.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||||
|
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||||
|
import { removeAtIndex, updateAtIndex } from "@saleor/utils/lists";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import CardSpacer from "../CardSpacer";
|
||||||
|
import MetadataCard, { MetadataCardProps } from "./MetadataCard";
|
||||||
|
import { EventDataAction, EventDataField } from "./types";
|
||||||
|
import { getDataKey, parseEventData } from "./utils";
|
||||||
|
|
||||||
|
export interface MetadataProps
|
||||||
|
extends Omit<MetadataCardProps, "data" | "isPrivate"> {
|
||||||
|
data: Record<"metadata" | "privateMetadata", MetadataInput[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Metadata: React.FC<MetadataProps> = ({ data, onChange }) => {
|
||||||
|
const change = (event: ChangeEvent, isPrivate: boolean) => {
|
||||||
|
const { action, field, fieldIndex, value } = parseEventData(event);
|
||||||
|
const key = getDataKey(isPrivate);
|
||||||
|
const dataToUpdate = data[key];
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
target: {
|
||||||
|
name: key,
|
||||||
|
value:
|
||||||
|
action === EventDataAction.update
|
||||||
|
? updateAtIndex(
|
||||||
|
{
|
||||||
|
...dataToUpdate[fieldIndex],
|
||||||
|
key:
|
||||||
|
field === EventDataField.name
|
||||||
|
? value
|
||||||
|
: dataToUpdate[fieldIndex].key,
|
||||||
|
value:
|
||||||
|
field === EventDataField.value
|
||||||
|
? value
|
||||||
|
: dataToUpdate[fieldIndex].value
|
||||||
|
},
|
||||||
|
dataToUpdate,
|
||||||
|
fieldIndex
|
||||||
|
)
|
||||||
|
: action === EventDataAction.add
|
||||||
|
? [
|
||||||
|
...dataToUpdate,
|
||||||
|
{
|
||||||
|
key: "",
|
||||||
|
value: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: removeAtIndex(dataToUpdate, fieldIndex)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MetadataCard
|
||||||
|
data={data?.metadata}
|
||||||
|
isPrivate={false}
|
||||||
|
onChange={event => change(event, false)}
|
||||||
|
/>
|
||||||
|
<CardSpacer />
|
||||||
|
<MetadataCard
|
||||||
|
data={data?.privateMetadata}
|
||||||
|
isPrivate={true}
|
||||||
|
onChange={event => change(event, true)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Metadata.displayName = "Metadata";
|
||||||
|
export default Metadata;
|
232
src/components/Metadata/MetadataCard.tsx
Normal file
232
src/components/Metadata/MetadataCard.tsx
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
import emptyMetadata from "@assets/images/empty-metadata.svg";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Card from "@material-ui/core/Card";
|
||||||
|
import CardActions from "@material-ui/core/CardActions";
|
||||||
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
|
import Table from "@material-ui/core/Table";
|
||||||
|
import TableBody from "@material-ui/core/TableBody";
|
||||||
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
|
import TableHead from "@material-ui/core/TableHead";
|
||||||
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import ToggleIcon from "@material-ui/icons/ArrowDropDown";
|
||||||
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import { FormChange } from "@saleor/hooks/useForm";
|
||||||
|
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import SVG from "react-inlinesvg";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import CardTitle from "../CardTitle";
|
||||||
|
import Skeleton from "../Skeleton";
|
||||||
|
import useStyles from "./styles";
|
||||||
|
import { EventDataAction, EventDataField } from "./types";
|
||||||
|
|
||||||
|
export interface MetadataCardProps {
|
||||||
|
data: MetadataInput[];
|
||||||
|
isPrivate: boolean;
|
||||||
|
onChange: FormChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nameSeparator = ":";
|
||||||
|
export const nameInputPrefix = EventDataField.name;
|
||||||
|
export const valueInputPrefix = EventDataField.value;
|
||||||
|
|
||||||
|
const MetadataCard: React.FC<MetadataCardProps> = ({
|
||||||
|
data,
|
||||||
|
isPrivate,
|
||||||
|
onChange
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const loaded = React.useRef(false);
|
||||||
|
const [expanded, setExpanded] = React.useState(true);
|
||||||
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data !== undefined) {
|
||||||
|
loaded.current = true;
|
||||||
|
if (data.length > 0) {
|
||||||
|
setExpanded(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data === undefined]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
data-test="metadataEditor"
|
||||||
|
data-test-is-private={isPrivate}
|
||||||
|
data-test-expanded={expanded}
|
||||||
|
>
|
||||||
|
<CardTitle
|
||||||
|
title={
|
||||||
|
isPrivate
|
||||||
|
? intl.formatMessage({
|
||||||
|
defaultMessage: "Private Metadata",
|
||||||
|
description: "header"
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
defaultMessage: "Metadata",
|
||||||
|
description: "header"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{data === undefined ? (
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton />
|
||||||
|
</CardContent>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CardContent className={classes.content}>
|
||||||
|
{data.length > 0 && (
|
||||||
|
<div className={classes.togglable}>
|
||||||
|
<Typography color="textSecondary" variant="body2">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{number,plural,one{{number} Field} other{{number} Fields}}"
|
||||||
|
description="number of metadata fields in model"
|
||||||
|
values={{
|
||||||
|
number: data.length
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
data-test="expand"
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
>
|
||||||
|
<ToggleIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
{expanded && (
|
||||||
|
<>
|
||||||
|
{data.length === 0 ? (
|
||||||
|
<div className={classes.emptyContainer}>
|
||||||
|
<SVG className={classes.emptyImage} src={emptyMetadata} />
|
||||||
|
<Typography color="textSecondary">
|
||||||
|
{isPrivate ? (
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="There is no private metadata created for this element."
|
||||||
|
description="empty metadata text"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="There is no metadata created for this element."
|
||||||
|
description="empty metadata text"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
<Typography color="textSecondary">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Use the button below to add new metadata field"
|
||||||
|
description="empty metadata text"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Table className={classes.table}>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className={classes.colNameHeader}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Field"
|
||||||
|
description="metadata field name, header"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colValue}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Value"
|
||||||
|
description="metadata field value, header"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colActionHeader}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Actions"
|
||||||
|
description="table action"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.map((field, fieldIndex) => (
|
||||||
|
<TableRow data-test="field" key={fieldIndex}>
|
||||||
|
<TableCell className={classes.colName}>
|
||||||
|
<TextField
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
input: classes.nameInput
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
name={`${nameInputPrefix}${nameSeparator}${fieldIndex}`}
|
||||||
|
fullWidth
|
||||||
|
onChange={onChange}
|
||||||
|
value={field.key}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colValue}>
|
||||||
|
<TextField
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
root: classes.input
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
multiline
|
||||||
|
name={`${valueInputPrefix}${nameSeparator}${fieldIndex}`}
|
||||||
|
fullWidth
|
||||||
|
onChange={onChange}
|
||||||
|
value={field.value}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colAction}>
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
data-test="deleteField"
|
||||||
|
data-test-id={fieldIndex}
|
||||||
|
onClick={() =>
|
||||||
|
onChange({
|
||||||
|
target: {
|
||||||
|
name: EventDataAction.delete,
|
||||||
|
value: fieldIndex
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
<CardActions>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
data-test="addField"
|
||||||
|
onClick={() =>
|
||||||
|
onChange({
|
||||||
|
target: {
|
||||||
|
name: EventDataAction.add,
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Add Field"
|
||||||
|
description="add metadata field,button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</CardActions>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MetadataCard.displayName = "MetadataCard";
|
||||||
|
export default MetadataCard;
|
22
src/components/Metadata/fixtures.ts
Normal file
22
src/components/Metadata/fixtures.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { MetadataProps } from "./Metadata";
|
||||||
|
|
||||||
|
export const props: MetadataProps = {
|
||||||
|
data: {
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
key: "key",
|
||||||
|
value: "value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key2",
|
||||||
|
value: '{\n "jsonValue": "some-value"\n}'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key3",
|
||||||
|
value: "some-value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
privateMetadata: []
|
||||||
|
},
|
||||||
|
onChange: () => undefined
|
||||||
|
};
|
5
src/components/Metadata/index.ts
Normal file
5
src/components/Metadata/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./Metadata";
|
||||||
|
export * from "./MetadataCard";
|
||||||
|
export * from "./types";
|
||||||
|
export { default } from "./Metadata";
|
||||||
|
export { default as MetadataCard } from "./MetadataCard";
|
66
src/components/Metadata/styles.ts
Normal file
66
src/components/Metadata/styles.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import makeStyles from "@material-ui/core/styles/makeStyles";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
theme => {
|
||||||
|
const colAction: React.CSSProperties = {
|
||||||
|
textAlign: "right",
|
||||||
|
width: 130
|
||||||
|
};
|
||||||
|
const colName: React.CSSProperties = {
|
||||||
|
width: 220
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
colAction: {
|
||||||
|
"&:last-child": {
|
||||||
|
...colAction,
|
||||||
|
paddingRight: theme.spacing()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colActionHeader: {
|
||||||
|
...colAction
|
||||||
|
},
|
||||||
|
colName: {
|
||||||
|
...colName,
|
||||||
|
verticalAlign: "top"
|
||||||
|
},
|
||||||
|
colNameHeader: {
|
||||||
|
...colName
|
||||||
|
},
|
||||||
|
colValue: {},
|
||||||
|
content: {
|
||||||
|
paddingBottom: 0,
|
||||||
|
paddingTop: theme.spacing()
|
||||||
|
},
|
||||||
|
emptyContainer: {
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
paddingBottom: theme.spacing(4),
|
||||||
|
paddingTop: theme.spacing(3),
|
||||||
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
emptyImage: {
|
||||||
|
display: "block",
|
||||||
|
marginBottom: theme.spacing(2)
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
padding: theme.spacing(0.5, 2)
|
||||||
|
},
|
||||||
|
nameInput: {
|
||||||
|
padding: `13px 16px`
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
tableLayout: "fixed"
|
||||||
|
},
|
||||||
|
togglable: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Metadata"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default useStyles;
|
21
src/components/Metadata/types.ts
Normal file
21
src/components/Metadata/types.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||||
|
|
||||||
|
export enum EventDataAction {
|
||||||
|
add = "add",
|
||||||
|
delete = "delete",
|
||||||
|
update = "update"
|
||||||
|
}
|
||||||
|
export enum EventDataField {
|
||||||
|
name = "name",
|
||||||
|
value = "value"
|
||||||
|
}
|
||||||
|
export interface EventData {
|
||||||
|
action: EventDataAction;
|
||||||
|
field: EventDataField | null;
|
||||||
|
fieldIndex: number | null;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
export interface MetadataFormData {
|
||||||
|
metadata: MetadataInput[];
|
||||||
|
privateMetadata: MetadataInput[];
|
||||||
|
}
|
42
src/components/Metadata/utils.ts
Normal file
42
src/components/Metadata/utils.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { ChangeEvent } from "@saleor/hooks/useForm";
|
||||||
|
|
||||||
|
import { nameSeparator } from "./MetadataCard";
|
||||||
|
import { EventData, EventDataAction, EventDataField } from "./types";
|
||||||
|
|
||||||
|
export function parseEventData(event: ChangeEvent): EventData {
|
||||||
|
let action: EventDataAction;
|
||||||
|
let field: EventDataField = null;
|
||||||
|
let fieldIndex: number = null;
|
||||||
|
let value: string = null;
|
||||||
|
|
||||||
|
if (event.target.name.includes(EventDataField.name)) {
|
||||||
|
action = EventDataAction.update;
|
||||||
|
field = EventDataField.name;
|
||||||
|
fieldIndex = parseInt(event.target.name.split(nameSeparator)[1], 0);
|
||||||
|
value = event.target.value;
|
||||||
|
}
|
||||||
|
if (event.target.name.includes(EventDataField.value)) {
|
||||||
|
action = EventDataAction.update;
|
||||||
|
field = EventDataField.value;
|
||||||
|
fieldIndex = parseInt(event.target.name.split(nameSeparator)[1], 0);
|
||||||
|
value = event.target.value;
|
||||||
|
}
|
||||||
|
if (event.target.name === EventDataAction.add) {
|
||||||
|
action = EventDataAction.add;
|
||||||
|
}
|
||||||
|
if (event.target.name === EventDataAction.delete) {
|
||||||
|
action = EventDataAction.delete;
|
||||||
|
fieldIndex = event.target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
action,
|
||||||
|
field,
|
||||||
|
fieldIndex,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDataKey(isPrivate: boolean) {
|
||||||
|
return isPrivate ? "privateMetadata" : "metadata";
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { metadataFragment } from "./metadata";
|
||||||
|
|
||||||
export const attributeFragment = gql`
|
export const attributeFragment = gql`
|
||||||
fragment AttributeFragment on Attribute {
|
fragment AttributeFragment on Attribute {
|
||||||
id
|
id
|
||||||
|
@ -13,8 +15,10 @@ export const attributeFragment = gql`
|
||||||
|
|
||||||
export const attributeDetailsFragment = gql`
|
export const attributeDetailsFragment = gql`
|
||||||
${attributeFragment}
|
${attributeFragment}
|
||||||
|
${metadataFragment}
|
||||||
fragment AttributeDetailsFragment on Attribute {
|
fragment AttributeDetailsFragment on Attribute {
|
||||||
...AttributeFragment
|
...AttributeFragment
|
||||||
|
...MetadataFragment
|
||||||
availableInGrid
|
availableInGrid
|
||||||
inputType
|
inputType
|
||||||
storefrontSearchPosition
|
storefrontSearchPosition
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { metadataFragment } from "./metadata";
|
||||||
|
|
||||||
export const categoryFragment = gql`
|
export const categoryFragment = gql`
|
||||||
fragment CategoryFragment on Category {
|
fragment CategoryFragment on Category {
|
||||||
id
|
id
|
||||||
|
@ -13,8 +15,10 @@ export const categoryFragment = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const categoryDetailsFragment = gql`
|
export const categoryDetailsFragment = gql`
|
||||||
|
${metadataFragment}
|
||||||
fragment CategoryDetailsFragment on Category {
|
fragment CategoryDetailsFragment on Category {
|
||||||
id
|
id
|
||||||
|
...MetadataFragment
|
||||||
backgroundImage {
|
backgroundImage {
|
||||||
alt
|
alt
|
||||||
url
|
url
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { metadataFragment } from "./metadata";
|
||||||
|
|
||||||
export const collectionFragment = gql`
|
export const collectionFragment = gql`
|
||||||
fragment CollectionFragment on Collection {
|
fragment CollectionFragment on Collection {
|
||||||
id
|
id
|
||||||
|
@ -10,8 +12,10 @@ export const collectionFragment = gql`
|
||||||
|
|
||||||
export const collectionDetailsFragment = gql`
|
export const collectionDetailsFragment = gql`
|
||||||
${collectionFragment}
|
${collectionFragment}
|
||||||
|
${metadataFragment}
|
||||||
fragment CollectionDetailsFragment on Collection {
|
fragment CollectionDetailsFragment on Collection {
|
||||||
...CollectionFragment
|
...CollectionFragment
|
||||||
|
...MetadataFragment
|
||||||
backgroundImage {
|
backgroundImage {
|
||||||
alt
|
alt
|
||||||
url
|
url
|
||||||
|
|
|
@ -134,3 +134,10 @@ export const pluginErrorFragment = gql`
|
||||||
field
|
field
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const metadataErrorFragment = gql`
|
||||||
|
fragment MetadataErrorFragment on MetadataError {
|
||||||
|
code
|
||||||
|
field
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const metadataFragment = gql`
|
||||||
key
|
key
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
fragment Metadata on ObjectWithMetadata {
|
fragment MetadataFragment on ObjectWithMetadata {
|
||||||
metadata {
|
metadata {
|
||||||
...MetadataItem
|
...MetadataItem
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { attributeFragment } from "./attributes";
|
import { attributeFragment } from "./attributes";
|
||||||
|
import { metadataFragment } from "./metadata";
|
||||||
|
|
||||||
export const productTypeFragment = gql`
|
export const productTypeFragment = gql`
|
||||||
fragment ProductTypeFragment on ProductType {
|
fragment ProductTypeFragment on ProductType {
|
||||||
|
@ -18,8 +19,10 @@ export const productTypeFragment = gql`
|
||||||
export const productTypeDetailsFragment = gql`
|
export const productTypeDetailsFragment = gql`
|
||||||
${attributeFragment}
|
${attributeFragment}
|
||||||
${productTypeFragment}
|
${productTypeFragment}
|
||||||
|
${metadataFragment}
|
||||||
fragment ProductTypeDetailsFragment on ProductType {
|
fragment ProductTypeDetailsFragment on ProductType {
|
||||||
...ProductTypeFragment
|
...ProductTypeFragment
|
||||||
|
...MetadataFragment
|
||||||
productAttributes {
|
productAttributes {
|
||||||
...AttributeFragment
|
...AttributeFragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { metadataFragment } from "./metadata";
|
||||||
import { weightFragment } from "./weight";
|
import { weightFragment } from "./weight";
|
||||||
|
|
||||||
export const stockFragment = gql`
|
export const stockFragment = gql`
|
||||||
|
@ -105,8 +106,10 @@ export const productFragmentDetails = gql`
|
||||||
${productVariantAttributesFragment}
|
${productVariantAttributesFragment}
|
||||||
${stockFragment}
|
${stockFragment}
|
||||||
${weightFragment}
|
${weightFragment}
|
||||||
|
${metadataFragment}
|
||||||
fragment Product on Product {
|
fragment Product on Product {
|
||||||
...ProductVariantAttributesFragment
|
...ProductVariantAttributesFragment
|
||||||
|
...MetadataFragment
|
||||||
name
|
name
|
||||||
descriptionJson
|
descriptionJson
|
||||||
seoTitle
|
seoTitle
|
||||||
|
@ -181,8 +184,10 @@ export const fragmentVariant = gql`
|
||||||
${fragmentProductImage}
|
${fragmentProductImage}
|
||||||
${stockFragment}
|
${stockFragment}
|
||||||
${weightFragment}
|
${weightFragment}
|
||||||
|
${metadataFragment}
|
||||||
fragment ProductVariant on ProductVariant {
|
fragment ProductVariant on ProductVariant {
|
||||||
id
|
id
|
||||||
|
...MetadataFragment
|
||||||
attributes {
|
attributes {
|
||||||
attribute {
|
attribute {
|
||||||
id
|
id
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType } from "./../../types/global
|
||||||
// GraphQL fragment: AttributeDetailsFragment
|
// GraphQL fragment: AttributeDetailsFragment
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface AttributeDetailsFragment_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttributeDetailsFragment_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AttributeDetailsFragment_values {
|
export interface AttributeDetailsFragment_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -24,6 +36,8 @@ export interface AttributeDetailsFragment {
|
||||||
visibleInStorefront: boolean;
|
visibleInStorefront: boolean;
|
||||||
filterableInDashboard: boolean;
|
filterableInDashboard: boolean;
|
||||||
filterableInStorefront: boolean;
|
filterableInStorefront: boolean;
|
||||||
|
metadata: (AttributeDetailsFragment_metadata | null)[];
|
||||||
|
privateMetadata: (AttributeDetailsFragment_privateMetadata | null)[];
|
||||||
availableInGrid: boolean;
|
availableInGrid: boolean;
|
||||||
inputType: AttributeInputTypeEnum | null;
|
inputType: AttributeInputTypeEnum | null;
|
||||||
storefrontSearchPosition: number;
|
storefrontSearchPosition: number;
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
// GraphQL fragment: CategoryDetailsFragment
|
// GraphQL fragment: CategoryDetailsFragment
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CategoryDetailsFragment_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CategoryDetailsFragment_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CategoryDetailsFragment_backgroundImage {
|
export interface CategoryDetailsFragment_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -20,6 +32,8 @@ export interface CategoryDetailsFragment_parent {
|
||||||
export interface CategoryDetailsFragment {
|
export interface CategoryDetailsFragment {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (CategoryDetailsFragment_metadata | null)[];
|
||||||
|
privateMetadata: (CategoryDetailsFragment_privateMetadata | null)[];
|
||||||
backgroundImage: CategoryDetailsFragment_backgroundImage | null;
|
backgroundImage: CategoryDetailsFragment_backgroundImage | null;
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
// GraphQL fragment: CollectionDetailsFragment
|
// GraphQL fragment: CollectionDetailsFragment
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CollectionDetailsFragment_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectionDetailsFragment_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CollectionDetailsFragment_backgroundImage {
|
export interface CollectionDetailsFragment_backgroundImage {
|
||||||
__typename: "Image";
|
__typename: "Image";
|
||||||
alt: string | null;
|
alt: string | null;
|
||||||
|
@ -17,6 +29,8 @@ export interface CollectionDetailsFragment {
|
||||||
id: string;
|
id: string;
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
|
metadata: (CollectionDetailsFragment_metadata | null)[];
|
||||||
|
privateMetadata: (CollectionDetailsFragment_privateMetadata | null)[];
|
||||||
backgroundImage: CollectionDetailsFragment_backgroundImage | null;
|
backgroundImage: CollectionDetailsFragment_backgroundImage | null;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
publicationDate: any | null;
|
publicationDate: any | null;
|
||||||
|
|
15
src/fragments/types/MetadataErrorFragment.ts
Normal file
15
src/fragments/types/MetadataErrorFragment.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MetadataErrorCode } from "./../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL fragment: MetadataErrorFragment
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface MetadataErrorFragment {
|
||||||
|
__typename: "MetadataError";
|
||||||
|
code: MetadataErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
25
src/fragments/types/MetadataFragment.ts
Normal file
25
src/fragments/types/MetadataFragment.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL fragment: MetadataFragment
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface MetadataFragment_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetadataFragment_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MetadataFragment {
|
||||||
|
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
|
||||||
|
metadata: (MetadataFragment_metadata | null)[];
|
||||||
|
privateMetadata: (MetadataFragment_privateMetadata | null)[];
|
||||||
|
}
|
|
@ -93,6 +93,18 @@ export interface Product_pricing {
|
||||||
priceRangeUndiscounted: Product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: Product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Product_category {
|
export interface Product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -180,6 +192,8 @@ export interface Product {
|
||||||
attributes: Product_attributes[];
|
attributes: Product_attributes[];
|
||||||
productType: Product_productType;
|
productType: Product_productType;
|
||||||
pricing: Product_pricing | null;
|
pricing: Product_pricing | null;
|
||||||
|
metadata: (Product_metadata | null)[];
|
||||||
|
privateMetadata: (Product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface ProductTypeDetailsFragment_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeDetailsFragment_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeDetailsFragment_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeDetailsFragment_productAttributes {
|
export interface ProductTypeDetailsFragment_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -47,6 +59,8 @@ export interface ProductTypeDetailsFragment {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: ProductTypeDetailsFragment_taxType | null;
|
taxType: ProductTypeDetailsFragment_taxType | null;
|
||||||
|
metadata: (ProductTypeDetailsFragment_metadata | null)[];
|
||||||
|
privateMetadata: (ProductTypeDetailsFragment_privateMetadata | null)[];
|
||||||
productAttributes: (ProductTypeDetailsFragment_productAttributes | null)[] | null;
|
productAttributes: (ProductTypeDetailsFragment_productAttributes | null)[] | null;
|
||||||
variantAttributes: (ProductTypeDetailsFragment_variantAttributes | null)[] | null;
|
variantAttributes: (ProductTypeDetailsFragment_variantAttributes | null)[] | null;
|
||||||
weight: ProductTypeDetailsFragment_weight | null;
|
weight: ProductTypeDetailsFragment_weight | null;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { WeightUnitsEnum } from "./../../types/globalTypes";
|
||||||
// GraphQL fragment: ProductVariant
|
// GraphQL fragment: ProductVariant
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ProductVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductVariant_attributes_attribute_values {
|
export interface ProductVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -114,6 +126,8 @@ export interface ProductVariant_weight {
|
||||||
export interface ProductVariant {
|
export interface ProductVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (ProductVariant_metadata | null)[];
|
||||||
|
privateMetadata: (ProductVariant_privateMetadata | null)[];
|
||||||
attributes: ProductVariant_attributes[];
|
attributes: ProductVariant_attributes[];
|
||||||
costPrice: ProductVariant_costPrice | null;
|
costPrice: ProductVariant_costPrice | null;
|
||||||
images: (ProductVariant_images | null)[] | null;
|
images: (ProductVariant_images | null)[] | null;
|
||||||
|
|
|
@ -5,6 +5,8 @@ import Container from "@saleor/components/Container";
|
||||||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
|
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
|
||||||
|
@ -13,6 +15,8 @@ import { sectionNames } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import { ListActions, ReorderEvent, UserError } from "@saleor/types";
|
import { ListActions, ReorderEvent, UserError } from "@saleor/types";
|
||||||
import { AttributeTypeEnum, WeightUnitsEnum } from "@saleor/types/globalTypes";
|
import { AttributeTypeEnum, WeightUnitsEnum } from "@saleor/types/globalTypes";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -30,7 +34,7 @@ interface ChoiceType {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductTypeForm {
|
export interface ProductTypeForm extends MetadataFormData {
|
||||||
name: string;
|
name: string;
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
|
@ -92,6 +96,12 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const [taxTypeDisplayName, setTaxTypeDisplayName] = useStateFromProps(
|
const [taxTypeDisplayName, setTaxTypeDisplayName] = useStateFromProps(
|
||||||
maybe(() => productType.taxType.description, "")
|
maybe(() => productType.taxType.description, "")
|
||||||
);
|
);
|
||||||
|
@ -104,7 +114,9 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
||||||
maybe(() => productType.isShippingRequired) !== undefined
|
maybe(() => productType.isShippingRequired) !== undefined
|
||||||
? productType.isShippingRequired
|
? productType.isShippingRequired
|
||||||
: false,
|
: false,
|
||||||
|
metadata: productType?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: maybe(() => productType.name) !== undefined ? productType.name : "",
|
name: maybe(() => productType.name) !== undefined ? productType.name : "",
|
||||||
|
privateMetadata: productType?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
productAttributes:
|
productAttributes:
|
||||||
maybe(() => productType.productAttributes) !== undefined
|
maybe(() => productType.productAttributes) !== undefined
|
||||||
? productType.productAttributes.map(attribute => ({
|
? productType.productAttributes.map(attribute => ({
|
||||||
|
@ -122,97 +134,117 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
||||||
: [],
|
: [],
|
||||||
weight: maybe(() => productType.weight.value)
|
weight: maybe(() => productType.weight.value)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (data: ProductTypeForm) => {
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
onSubmit({
|
||||||
|
...data,
|
||||||
|
metadata,
|
||||||
|
privateMetadata
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={formInitialData} onSubmit={onSubmit} confirmLeave>
|
<Form initial={formInitialData} onSubmit={handleSubmit} confirmLeave>
|
||||||
{({ change, data, hasChanged, submit }) => (
|
{({ change, data, hasChanged, submit }) => {
|
||||||
<Container>
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
<AppHeader onBack={onBack}>
|
|
||||||
{intl.formatMessage(sectionNames.productTypes)}
|
return (
|
||||||
</AppHeader>
|
<Container>
|
||||||
<PageHeader title={pageTitle} />
|
<AppHeader onBack={onBack}>
|
||||||
<Grid>
|
{intl.formatMessage(sectionNames.productTypes)}
|
||||||
<div>
|
</AppHeader>
|
||||||
<ProductTypeDetails
|
<PageHeader title={pageTitle} />
|
||||||
data={data}
|
<Grid>
|
||||||
disabled={disabled}
|
<div>
|
||||||
errors={errors}
|
<ProductTypeDetails
|
||||||
onChange={change}
|
data={data}
|
||||||
/>
|
disabled={disabled}
|
||||||
<CardSpacer />
|
errors={errors}
|
||||||
<ProductTypeTaxes
|
onChange={change}
|
||||||
disabled={disabled}
|
/>
|
||||||
data={data}
|
<CardSpacer />
|
||||||
taxTypes={taxTypes}
|
<ProductTypeTaxes
|
||||||
taxTypeDisplayName={taxTypeDisplayName}
|
disabled={disabled}
|
||||||
onChange={event =>
|
data={data}
|
||||||
handleTaxTypeChange(
|
taxTypes={taxTypes}
|
||||||
event,
|
taxTypeDisplayName={taxTypeDisplayName}
|
||||||
taxTypes,
|
onChange={event =>
|
||||||
change,
|
handleTaxTypeChange(
|
||||||
setTaxTypeDisplayName
|
event,
|
||||||
)
|
taxTypes,
|
||||||
}
|
change,
|
||||||
/>
|
setTaxTypeDisplayName
|
||||||
<CardSpacer />
|
)
|
||||||
<ProductTypeAttributes
|
}
|
||||||
attributes={maybe(() => productType.productAttributes)}
|
/>
|
||||||
disabled={disabled}
|
<CardSpacer />
|
||||||
type={AttributeTypeEnum.PRODUCT}
|
<ProductTypeAttributes
|
||||||
onAttributeAssign={onAttributeAdd}
|
attributes={maybe(() => productType.productAttributes)}
|
||||||
onAttributeClick={onAttributeClick}
|
disabled={disabled}
|
||||||
onAttributeReorder={(event: ReorderEvent) =>
|
type={AttributeTypeEnum.PRODUCT}
|
||||||
onAttributeReorder(event, AttributeTypeEnum.PRODUCT)
|
onAttributeAssign={onAttributeAdd}
|
||||||
}
|
onAttributeClick={onAttributeClick}
|
||||||
onAttributeUnassign={onAttributeUnassign}
|
onAttributeReorder={(event: ReorderEvent) =>
|
||||||
{...productAttributeList}
|
onAttributeReorder(event, AttributeTypeEnum.PRODUCT)
|
||||||
/>
|
}
|
||||||
<CardSpacer />
|
onAttributeUnassign={onAttributeUnassign}
|
||||||
<ControlledSwitch
|
{...productAttributeList}
|
||||||
checked={data.hasVariants}
|
/>
|
||||||
disabled={disabled}
|
<CardSpacer />
|
||||||
label={intl.formatMessage({
|
<ControlledSwitch
|
||||||
defaultMessage: "Product type uses Variant Attributes",
|
checked={data.hasVariants}
|
||||||
description: "switch button"
|
disabled={disabled}
|
||||||
})}
|
label={intl.formatMessage({
|
||||||
name="hasVariants"
|
defaultMessage: "Product type uses Variant Attributes",
|
||||||
onChange={event => onHasVariantsToggle(event.target.value)}
|
description: "switch button"
|
||||||
/>
|
})}
|
||||||
{data.hasVariants && (
|
name="hasVariants"
|
||||||
<>
|
onChange={event => onHasVariantsToggle(event.target.value)}
|
||||||
<CardSpacer />
|
/>
|
||||||
<ProductTypeAttributes
|
{data.hasVariants && (
|
||||||
attributes={maybe(() => productType.variantAttributes)}
|
<>
|
||||||
disabled={disabled}
|
<CardSpacer />
|
||||||
type={AttributeTypeEnum.VARIANT}
|
<ProductTypeAttributes
|
||||||
onAttributeAssign={onAttributeAdd}
|
attributes={maybe(() => productType.variantAttributes)}
|
||||||
onAttributeClick={onAttributeClick}
|
disabled={disabled}
|
||||||
onAttributeReorder={(event: ReorderEvent) =>
|
type={AttributeTypeEnum.VARIANT}
|
||||||
onAttributeReorder(event, AttributeTypeEnum.VARIANT)
|
onAttributeAssign={onAttributeAdd}
|
||||||
}
|
onAttributeClick={onAttributeClick}
|
||||||
onAttributeUnassign={onAttributeUnassign}
|
onAttributeReorder={(event: ReorderEvent) =>
|
||||||
{...variantAttributeList}
|
onAttributeReorder(event, AttributeTypeEnum.VARIANT)
|
||||||
/>
|
}
|
||||||
</>
|
onAttributeUnassign={onAttributeUnassign}
|
||||||
)}
|
{...variantAttributeList}
|
||||||
</div>
|
/>
|
||||||
<div>
|
</>
|
||||||
<ProductTypeShipping
|
)}
|
||||||
disabled={disabled}
|
<CardSpacer />
|
||||||
data={data}
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
weightUnit={productType?.weight?.unit || defaultWeightUnit}
|
</div>
|
||||||
onChange={change}
|
<div>
|
||||||
/>
|
<ProductTypeShipping
|
||||||
</div>
|
disabled={disabled}
|
||||||
</Grid>
|
data={data}
|
||||||
<SaveButtonBar
|
weightUnit={productType?.weight?.unit || defaultWeightUnit}
|
||||||
onCancel={onBack}
|
onChange={change}
|
||||||
onDelete={onDelete}
|
/>
|
||||||
onSave={submit}
|
</div>
|
||||||
disabled={disabled || !hasChanged}
|
</Grid>
|
||||||
state={saveButtonBarState}
|
<SaveButtonBar
|
||||||
/>
|
onCancel={onBack}
|
||||||
</Container>
|
onDelete={onDelete}
|
||||||
)}
|
onSave={submit}
|
||||||
|
disabled={disabled || !hasChanged}
|
||||||
|
state={saveButtonBarState}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,6 @@ import {
|
||||||
ProductTypeAttributeReorderMutation,
|
ProductTypeAttributeReorderMutation,
|
||||||
TypedAssignAttributeMutation,
|
TypedAssignAttributeMutation,
|
||||||
TypedProductTypeDeleteMutation,
|
TypedProductTypeDeleteMutation,
|
||||||
TypedProductTypeUpdateMutation,
|
|
||||||
TypedUnassignAttributeMutation
|
TypedUnassignAttributeMutation
|
||||||
} from "../mutations";
|
} from "../mutations";
|
||||||
import {
|
import {
|
||||||
|
@ -27,10 +26,6 @@ import {
|
||||||
ProductTypeDelete,
|
ProductTypeDelete,
|
||||||
ProductTypeDeleteVariables
|
ProductTypeDeleteVariables
|
||||||
} from "../types/ProductTypeDelete";
|
} from "../types/ProductTypeDelete";
|
||||||
import {
|
|
||||||
ProductTypeUpdate,
|
|
||||||
ProductTypeUpdateVariables
|
|
||||||
} from "../types/ProductTypeUpdate";
|
|
||||||
import {
|
import {
|
||||||
UnassignAttribute,
|
UnassignAttribute,
|
||||||
UnassignAttributeVariables
|
UnassignAttributeVariables
|
||||||
|
@ -75,17 +70,12 @@ interface ProductTypeOperationsProps {
|
||||||
ProductTypeAttributeReorder,
|
ProductTypeAttributeReorder,
|
||||||
ProductTypeAttributeReorderVariables
|
ProductTypeAttributeReorderVariables
|
||||||
>;
|
>;
|
||||||
updateProductType: PartialMutationProviderOutput<
|
|
||||||
ProductTypeUpdate,
|
|
||||||
ProductTypeUpdateVariables
|
|
||||||
>;
|
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
productType: ProductTypeDetailsFragment;
|
productType: ProductTypeDetailsFragment;
|
||||||
onAssignAttribute: (data: AssignAttribute) => void;
|
onAssignAttribute: (data: AssignAttribute) => void;
|
||||||
onUnassignAttribute: (data: UnassignAttribute) => void;
|
onUnassignAttribute: (data: UnassignAttribute) => void;
|
||||||
onProductTypeAttributeReorder: (data: ProductTypeAttributeReorder) => void;
|
onProductTypeAttributeReorder: (data: ProductTypeAttributeReorder) => void;
|
||||||
onProductTypeDelete: (data: ProductTypeDelete) => void;
|
onProductTypeDelete: (data: ProductTypeDelete) => void;
|
||||||
onProductTypeUpdate: (data: ProductTypeUpdate) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
||||||
|
@ -94,85 +84,72 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
||||||
onAssignAttribute,
|
onAssignAttribute,
|
||||||
onUnassignAttribute,
|
onUnassignAttribute,
|
||||||
onProductTypeAttributeReorder,
|
onProductTypeAttributeReorder,
|
||||||
onProductTypeDelete,
|
onProductTypeDelete
|
||||||
onProductTypeUpdate
|
|
||||||
}) => (
|
}) => (
|
||||||
<TypedProductTypeDeleteMutation onCompleted={onProductTypeDelete}>
|
<TypedProductTypeDeleteMutation onCompleted={onProductTypeDelete}>
|
||||||
{(...deleteProductType) => (
|
{(...deleteProductType) => (
|
||||||
<TypedProductTypeUpdateMutation onCompleted={onProductTypeUpdate}>
|
<TypedAssignAttributeMutation onCompleted={onAssignAttribute}>
|
||||||
{(...updateProductType) => (
|
{(...assignAttribute) => (
|
||||||
<TypedAssignAttributeMutation onCompleted={onAssignAttribute}>
|
<TypedUnassignAttributeMutation onCompleted={onUnassignAttribute}>
|
||||||
{(...assignAttribute) => (
|
{(...unassignAttribute) => (
|
||||||
<TypedUnassignAttributeMutation onCompleted={onUnassignAttribute}>
|
<ProductTypeAttributeReorderMutation
|
||||||
{(...unassignAttribute) => (
|
onCompleted={onProductTypeAttributeReorder}
|
||||||
<ProductTypeAttributeReorderMutation
|
>
|
||||||
onCompleted={onProductTypeAttributeReorder}
|
{(reorderAttributeMutation, reorderAttributeMutationResult) => {
|
||||||
>
|
const reorderAttributeMutationFn: MutationFunction<
|
||||||
{(
|
ProductTypeAttributeReorder,
|
||||||
reorderAttributeMutation,
|
ProductTypeAttributeReorderVariables
|
||||||
reorderAttributeMutationResult
|
> = opts => {
|
||||||
) => {
|
const optimisticResponse: ProductTypeAttributeReorder = {
|
||||||
const reorderAttributeMutationFn: MutationFunction<
|
productTypeReorderAttributes: {
|
||||||
ProductTypeAttributeReorder,
|
__typename: "ProductTypeReorderAttributes" as "ProductTypeReorderAttributes",
|
||||||
ProductTypeAttributeReorderVariables
|
errors: [],
|
||||||
> = opts => {
|
productType: {
|
||||||
const optimisticResponse: ProductTypeAttributeReorder = {
|
...productType,
|
||||||
productTypeReorderAttributes: {
|
productAttributes:
|
||||||
__typename: "ProductTypeReorderAttributes" as "ProductTypeReorderAttributes",
|
opts.variables.type === AttributeTypeEnum.PRODUCT
|
||||||
errors: [],
|
? moveAttribute(
|
||||||
productType: {
|
productType.productAttributes,
|
||||||
...productType,
|
opts.variables.move
|
||||||
productAttributes:
|
)
|
||||||
opts.variables.type ===
|
: productType.productAttributes,
|
||||||
AttributeTypeEnum.PRODUCT
|
variantAttributes:
|
||||||
? moveAttribute(
|
opts.variables.type === AttributeTypeEnum.VARIANT
|
||||||
productType.productAttributes,
|
? moveAttribute(
|
||||||
opts.variables.move
|
productType.variantAttributes,
|
||||||
)
|
opts.variables.move
|
||||||
: productType.productAttributes,
|
)
|
||||||
variantAttributes:
|
: productType.variantAttributes
|
||||||
opts.variables.type ===
|
}
|
||||||
AttributeTypeEnum.VARIANT
|
}
|
||||||
? moveAttribute(
|
};
|
||||||
productType.variantAttributes,
|
return reorderAttributeMutation({
|
||||||
opts.variables.move
|
...opts,
|
||||||
)
|
optimisticResponse
|
||||||
: productType.variantAttributes
|
});
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
return reorderAttributeMutation({
|
|
||||||
...opts,
|
|
||||||
optimisticResponse
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return children({
|
return children({
|
||||||
assignAttribute: getMutationProviderData(
|
assignAttribute: getMutationProviderData(
|
||||||
...assignAttribute
|
...assignAttribute
|
||||||
),
|
),
|
||||||
deleteProductType: getMutationProviderData(
|
deleteProductType: getMutationProviderData(
|
||||||
...deleteProductType
|
...deleteProductType
|
||||||
),
|
),
|
||||||
reorderAttribute: getMutationProviderData(
|
reorderAttribute: getMutationProviderData(
|
||||||
reorderAttributeMutationFn,
|
reorderAttributeMutationFn,
|
||||||
reorderAttributeMutationResult
|
reorderAttributeMutationResult
|
||||||
),
|
),
|
||||||
unassignAttribute: getMutationProviderData(
|
unassignAttribute: getMutationProviderData(
|
||||||
...unassignAttribute
|
...unassignAttribute
|
||||||
),
|
)
|
||||||
updateProductType: getMutationProviderData(
|
});
|
||||||
...updateProductType
|
}}
|
||||||
)
|
</ProductTypeAttributeReorderMutation>
|
||||||
});
|
|
||||||
}}
|
|
||||||
</ProductTypeAttributeReorderMutation>
|
|
||||||
)}
|
|
||||||
</TypedUnassignAttributeMutation>
|
|
||||||
)}
|
)}
|
||||||
</TypedAssignAttributeMutation>
|
</TypedUnassignAttributeMutation>
|
||||||
)}
|
)}
|
||||||
</TypedProductTypeUpdateMutation>
|
</TypedAssignAttributeMutation>
|
||||||
)}
|
)}
|
||||||
</TypedProductTypeDeleteMutation>
|
</TypedProductTypeDeleteMutation>
|
||||||
);
|
);
|
||||||
|
|
|
@ -550,7 +550,15 @@ export const productType: ProductTypeDetails_productType = {
|
||||||
hasVariants: false,
|
hasVariants: false,
|
||||||
id: "UHJvZHVjdFR5cGU6NQ==",
|
id: "UHJvZHVjdFR5cGU6NQ==",
|
||||||
isShippingRequired: false,
|
isShippingRequired: false,
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "E-books",
|
name: "E-books",
|
||||||
|
privateMetadata: [],
|
||||||
productAttributes: [
|
productAttributes: [
|
||||||
{
|
{
|
||||||
__typename: "Attribute" as "Attribute",
|
__typename: "Attribute" as "Attribute",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { productTypeDetailsFragment } from "@saleor/fragments/productTypes";
|
import { productTypeDetailsFragment } from "@saleor/fragments/productTypes";
|
||||||
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedMutation } from "../mutations";
|
import { TypedMutation } from "../mutations";
|
||||||
|
@ -78,7 +79,7 @@ export const productTypeUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductTypeUpdateMutation = TypedMutation<
|
export const useProductTypeUpdateMutation = makeMutation<
|
||||||
ProductTypeUpdate,
|
ProductTypeUpdate,
|
||||||
ProductTypeUpdateVariables
|
ProductTypeUpdateVariables
|
||||||
>(productTypeUpdateMutation);
|
>(productTypeUpdateMutation);
|
||||||
|
|
|
@ -20,6 +20,18 @@ export interface AssignAttribute_attributeAssign_productType_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssignAttribute_attributeAssign_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignAttribute_attributeAssign_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AssignAttribute_attributeAssign_productType_productAttributes {
|
export interface AssignAttribute_attributeAssign_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -53,6 +65,8 @@ export interface AssignAttribute_attributeAssign_productType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: AssignAttribute_attributeAssign_productType_taxType | null;
|
taxType: AssignAttribute_attributeAssign_productType_taxType | null;
|
||||||
|
metadata: (AssignAttribute_attributeAssign_productType_metadata | null)[];
|
||||||
|
privateMetadata: (AssignAttribute_attributeAssign_productType_privateMetadata | null)[];
|
||||||
productAttributes: (AssignAttribute_attributeAssign_productType_productAttributes | null)[] | null;
|
productAttributes: (AssignAttribute_attributeAssign_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (AssignAttribute_attributeAssign_productType_variantAttributes | null)[] | null;
|
variantAttributes: (AssignAttribute_attributeAssign_productType_variantAttributes | null)[] | null;
|
||||||
weight: AssignAttribute_attributeAssign_productType_weight | null;
|
weight: AssignAttribute_attributeAssign_productType_weight | null;
|
||||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_productAttributes {
|
export interface ProductTypeAttributeReorder_productTypeReorderAttributes_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -53,6 +65,8 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_taxType | null;
|
taxType: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_taxType | null;
|
||||||
|
metadata: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_metadata | null)[];
|
||||||
|
privateMetadata: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_privateMetadata | null)[];
|
||||||
productAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_productAttributes | null)[] | null;
|
productAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_variantAttributes | null)[] | null;
|
variantAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_variantAttributes | null)[] | null;
|
||||||
weight: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_weight | null;
|
weight: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_weight | null;
|
||||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeCreate_productTypeCreate_productType_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeCreate_productTypeCreate_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeCreate_productTypeCreate_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeCreate_productTypeCreate_productType_productAttributes {
|
export interface ProductTypeCreate_productTypeCreate_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -53,6 +65,8 @@ export interface ProductTypeCreate_productTypeCreate_productType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: ProductTypeCreate_productTypeCreate_productType_taxType | null;
|
taxType: ProductTypeCreate_productTypeCreate_productType_taxType | null;
|
||||||
|
metadata: (ProductTypeCreate_productTypeCreate_productType_metadata | null)[];
|
||||||
|
privateMetadata: (ProductTypeCreate_productTypeCreate_productType_privateMetadata | null)[];
|
||||||
productAttributes: (ProductTypeCreate_productTypeCreate_productType_productAttributes | null)[] | null;
|
productAttributes: (ProductTypeCreate_productTypeCreate_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (ProductTypeCreate_productTypeCreate_productType_variantAttributes | null)[] | null;
|
variantAttributes: (ProductTypeCreate_productTypeCreate_productType_variantAttributes | null)[] | null;
|
||||||
weight: ProductTypeCreate_productTypeCreate_productType_weight | null;
|
weight: ProductTypeCreate_productTypeCreate_productType_weight | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface ProductTypeDetails_productType_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeDetails_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeDetails_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeDetails_productType_productAttributes {
|
export interface ProductTypeDetails_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -47,6 +59,8 @@ export interface ProductTypeDetails_productType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: ProductTypeDetails_productType_taxType | null;
|
taxType: ProductTypeDetails_productType_taxType | null;
|
||||||
|
metadata: (ProductTypeDetails_productType_metadata | null)[];
|
||||||
|
privateMetadata: (ProductTypeDetails_productType_privateMetadata | null)[];
|
||||||
productAttributes: (ProductTypeDetails_productType_productAttributes | null)[] | null;
|
productAttributes: (ProductTypeDetails_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (ProductTypeDetails_productType_variantAttributes | null)[] | null;
|
variantAttributes: (ProductTypeDetails_productType_variantAttributes | null)[] | null;
|
||||||
weight: ProductTypeDetails_productType_weight | null;
|
weight: ProductTypeDetails_productType_weight | null;
|
||||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeUpdate_productTypeUpdate_productType_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeUpdate_productTypeUpdate_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductTypeUpdate_productTypeUpdate_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeUpdate_productTypeUpdate_productType_productAttributes {
|
export interface ProductTypeUpdate_productTypeUpdate_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -53,6 +65,8 @@ export interface ProductTypeUpdate_productTypeUpdate_productType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: ProductTypeUpdate_productTypeUpdate_productType_taxType | null;
|
taxType: ProductTypeUpdate_productTypeUpdate_productType_taxType | null;
|
||||||
|
metadata: (ProductTypeUpdate_productTypeUpdate_productType_metadata | null)[];
|
||||||
|
privateMetadata: (ProductTypeUpdate_productTypeUpdate_productType_privateMetadata | null)[];
|
||||||
productAttributes: (ProductTypeUpdate_productTypeUpdate_productType_productAttributes | null)[] | null;
|
productAttributes: (ProductTypeUpdate_productTypeUpdate_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (ProductTypeUpdate_productTypeUpdate_productType_variantAttributes | null)[] | null;
|
variantAttributes: (ProductTypeUpdate_productTypeUpdate_productType_variantAttributes | null)[] | null;
|
||||||
weight: ProductTypeUpdate_productTypeUpdate_productType_weight | null;
|
weight: ProductTypeUpdate_productTypeUpdate_productType_weight | null;
|
||||||
|
|
|
@ -20,6 +20,18 @@ export interface UnassignAttribute_attributeUnassign_productType_taxType {
|
||||||
taxCode: string | null;
|
taxCode: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnassignAttribute_attributeUnassign_productType_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnassignAttribute_attributeUnassign_productType_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UnassignAttribute_attributeUnassign_productType_productAttributes {
|
export interface UnassignAttribute_attributeUnassign_productType_productAttributes {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -53,6 +65,8 @@ export interface UnassignAttribute_attributeUnassign_productType {
|
||||||
hasVariants: boolean;
|
hasVariants: boolean;
|
||||||
isShippingRequired: boolean;
|
isShippingRequired: boolean;
|
||||||
taxType: UnassignAttribute_attributeUnassign_productType_taxType | null;
|
taxType: UnassignAttribute_attributeUnassign_productType_taxType | null;
|
||||||
|
metadata: (UnassignAttribute_attributeUnassign_productType_metadata | null)[];
|
||||||
|
privateMetadata: (UnassignAttribute_attributeUnassign_productType_privateMetadata | null)[];
|
||||||
productAttributes: (UnassignAttribute_attributeUnassign_productType_productAttributes | null)[] | null;
|
productAttributes: (UnassignAttribute_attributeUnassign_productType_productAttributes | null)[] | null;
|
||||||
variantAttributes: (UnassignAttribute_attributeUnassign_productType_variantAttributes | null)[] | null;
|
variantAttributes: (UnassignAttribute_attributeUnassign_productType_variantAttributes | null)[] | null;
|
||||||
weight: UnassignAttribute_attributeUnassign_productType_weight | null;
|
weight: UnassignAttribute_attributeUnassign_productType_weight | null;
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { UserError } from "../../../types";
|
|
||||||
|
|
||||||
interface ProductTypeUpdateErrorsState {
|
|
||||||
addAttributeErrors: UserError[];
|
|
||||||
editAttributeErrors: UserError[];
|
|
||||||
formErrors: UserError[];
|
|
||||||
}
|
|
||||||
interface ProductTypeUpdateErrorsProps {
|
|
||||||
children: (props: {
|
|
||||||
errors: ProductTypeUpdateErrorsState;
|
|
||||||
set: {
|
|
||||||
addAttributeErrors: (errors: UserError[]) => void;
|
|
||||||
editAttributeErrors: (errors: UserError[]) => void;
|
|
||||||
formErrors: (errors: UserError[]) => void;
|
|
||||||
};
|
|
||||||
}) => React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProductTypeUpdateErrors extends React.Component<
|
|
||||||
ProductTypeUpdateErrorsProps,
|
|
||||||
ProductTypeUpdateErrorsState
|
|
||||||
> {
|
|
||||||
state: ProductTypeUpdateErrorsState = {
|
|
||||||
addAttributeErrors: [],
|
|
||||||
editAttributeErrors: [],
|
|
||||||
formErrors: []
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return this.props.children({
|
|
||||||
errors: this.state,
|
|
||||||
set: {
|
|
||||||
addAttributeErrors: (addAttributeErrors: UserError[]) =>
|
|
||||||
this.setState({ addAttributeErrors }),
|
|
||||||
editAttributeErrors: (editAttributeErrors: UserError[]) =>
|
|
||||||
this.setState({ editAttributeErrors }),
|
|
||||||
formErrors: (formErrors: UserError[]) => this.setState({ formErrors })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default ProductTypeUpdateErrors;
|
|
|
@ -9,8 +9,14 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
import AssignAttributeDialog from "@saleor/productTypes/components/AssignAttributeDialog";
|
import AssignAttributeDialog from "@saleor/productTypes/components/AssignAttributeDialog";
|
||||||
|
import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import { AttributeTypeEnum } from "@saleor/types/globalTypes";
|
import { AttributeTypeEnum } from "@saleor/types/globalTypes";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -25,14 +31,12 @@ import useAvailableAttributeSearch from "../../hooks/useAvailableAttributeSearch
|
||||||
import { TypedProductTypeDetailsQuery } from "../../queries";
|
import { TypedProductTypeDetailsQuery } from "../../queries";
|
||||||
import { AssignAttribute } from "../../types/AssignAttribute";
|
import { AssignAttribute } from "../../types/AssignAttribute";
|
||||||
import { ProductTypeDelete } from "../../types/ProductTypeDelete";
|
import { ProductTypeDelete } from "../../types/ProductTypeDelete";
|
||||||
import { ProductTypeUpdate as ProductTypeUpdateMutation } from "../../types/ProductTypeUpdate";
|
|
||||||
import { UnassignAttribute } from "../../types/UnassignAttribute";
|
import { UnassignAttribute } from "../../types/UnassignAttribute";
|
||||||
import {
|
import {
|
||||||
productTypeListUrl,
|
productTypeListUrl,
|
||||||
productTypeUrl,
|
productTypeUrl,
|
||||||
ProductTypeUrlQueryParams
|
ProductTypeUrlQueryParams
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import { ProductTypeUpdateErrors } from "./errors";
|
|
||||||
|
|
||||||
interface ProductTypeUpdateProps {
|
interface ProductTypeUpdateProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -54,361 +58,367 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const [errors, setErrors] = React.useState({
|
||||||
|
addAttributeErrors: [],
|
||||||
|
editAttributeErrors: [],
|
||||||
|
formErrors: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
updateProductType,
|
||||||
|
updateProductTypeOpts
|
||||||
|
] = useProductTypeUpdateMutation({
|
||||||
|
onCompleted: updateData => {
|
||||||
|
if (
|
||||||
|
!updateData.productTypeUpdate.errors ||
|
||||||
|
updateData.productTypeUpdate.errors.length === 0
|
||||||
|
) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
updateData.productTypeUpdate.errors !== null &&
|
||||||
|
updateData.productTypeUpdate.errors.length > 0
|
||||||
|
) {
|
||||||
|
setErrors(prevErrors => ({
|
||||||
|
...prevErrors,
|
||||||
|
formErrors: updateData.productTypeUpdate.errors
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const handleBack = () => navigate(productTypeListUrl());
|
const handleBack = () => navigate(productTypeListUrl());
|
||||||
|
|
||||||
|
const handleProductTypeUpdate = async (formData: ProductTypeForm) => {
|
||||||
|
const result = await updateProductType({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
hasVariants: formData.hasVariants,
|
||||||
|
isShippingRequired: formData.isShippingRequired,
|
||||||
|
name: formData.name,
|
||||||
|
productAttributes: formData.productAttributes.map(
|
||||||
|
choice => choice.value
|
||||||
|
),
|
||||||
|
taxCode: formData.taxType,
|
||||||
|
variantAttributes: formData.variantAttributes.map(
|
||||||
|
choice => choice.value
|
||||||
|
),
|
||||||
|
weight: formData.weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.data.productTypeUpdate.errors;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductTypeUpdateErrors>
|
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
||||||
{({ errors, set: setErrors }) => (
|
{({ data, loading: dataLoading }) => {
|
||||||
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
const productType = data?.productType;
|
||||||
{({ data, loading: dataLoading }) => {
|
|
||||||
const productType = data?.productType;
|
|
||||||
|
|
||||||
if (productType === null) {
|
if (productType === null) {
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
return <NotFoundPage onBack={handleBack} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeModal = () => navigate(productTypeUrl(id), true);
|
const closeModal = () => navigate(productTypeUrl(id), true);
|
||||||
|
|
||||||
const handleAttributeAssignSuccess = (data: AssignAttribute) => {
|
const handleAttributeAssignSuccess = (data: AssignAttribute) => {
|
||||||
if (data.attributeAssign.errors.length === 0) {
|
if (data.attributeAssign.errors.length === 0) {
|
||||||
notify({
|
notify({
|
||||||
status: "success",
|
status: "success",
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
} else if (
|
||||||
|
data.attributeAssign.errors !== null &&
|
||||||
|
data.attributeAssign.errors.length > 0
|
||||||
|
) {
|
||||||
|
setErrors(prevErrors => ({
|
||||||
|
...prevErrors,
|
||||||
|
addAttributeErrors: data.attributeAssign.errors
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleAttributeUnassignSuccess = (data: UnassignAttribute) => {
|
||||||
|
if (data.attributeUnassign.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
closeModal();
|
||||||
|
productAttributeListActions.reset();
|
||||||
|
variantAttributeListActions.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleProductTypeDeleteSuccess = (
|
||||||
|
deleteData: ProductTypeDelete
|
||||||
|
) => {
|
||||||
|
if (deleteData.productTypeDelete.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Product type deleted"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(productTypeListUrl(), true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
data?.productType,
|
||||||
|
handleProductTypeUpdate,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProductTypeOperations
|
||||||
|
productType={maybe(() => data.productType)}
|
||||||
|
onAssignAttribute={handleAttributeAssignSuccess}
|
||||||
|
onUnassignAttribute={handleAttributeUnassignSuccess}
|
||||||
|
onProductTypeDelete={handleProductTypeDeleteSuccess}
|
||||||
|
onProductTypeAttributeReorder={() => undefined}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
assignAttribute,
|
||||||
|
deleteProductType,
|
||||||
|
unassignAttribute,
|
||||||
|
reorderAttribute
|
||||||
|
}) => {
|
||||||
|
const handleProductTypeDelete = () =>
|
||||||
|
deleteProductType.mutate({ id });
|
||||||
|
const handleProductTypeVariantsToggle = (hasVariants: boolean) =>
|
||||||
|
updateProductType({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
hasVariants
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
closeModal();
|
const handleAssignAttribute = () =>
|
||||||
} else if (
|
assignAttribute.mutate({
|
||||||
data.attributeAssign.errors !== null &&
|
id,
|
||||||
data.attributeAssign.errors.length > 0
|
operations: params.ids.map(id => ({
|
||||||
) {
|
id,
|
||||||
setErrors.addAttributeErrors(data.attributeAssign.errors);
|
type: AttributeTypeEnum[params.type]
|
||||||
}
|
}))
|
||||||
};
|
|
||||||
const handleAttributeUnassignSuccess = (
|
|
||||||
data: UnassignAttribute
|
|
||||||
) => {
|
|
||||||
if (data.attributeUnassign.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
});
|
||||||
closeModal();
|
|
||||||
productAttributeListActions.reset();
|
const handleAttributeUnassign = () =>
|
||||||
variantAttributeListActions.reset();
|
unassignAttribute.mutate({
|
||||||
}
|
id,
|
||||||
};
|
ids: [params.id]
|
||||||
const handleProductTypeDeleteSuccess = (
|
|
||||||
deleteData: ProductTypeDelete
|
|
||||||
) => {
|
|
||||||
if (deleteData.productTypeDelete.errors.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Product type deleted"
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
navigate(productTypeListUrl(), true);
|
|
||||||
}
|
const handleBulkAttributeUnassign = () =>
|
||||||
};
|
unassignAttribute.mutate({
|
||||||
const handleProductTypeUpdateSuccess = (
|
id,
|
||||||
updateData: ProductTypeUpdateMutation
|
ids: params.ids
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
!updateData.productTypeUpdate.errors ||
|
|
||||||
updateData.productTypeUpdate.errors.length === 0
|
|
||||||
) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
});
|
||||||
} else if (
|
|
||||||
updateData.productTypeUpdate.errors !== null &&
|
|
||||||
updateData.productTypeUpdate.errors.length > 0
|
|
||||||
) {
|
|
||||||
setErrors.formErrors(updateData.productTypeUpdate.errors);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const loading = updateProductTypeOpts.loading || dataLoading;
|
||||||
<ProductTypeOperations
|
|
||||||
productType={maybe(() => data.productType)}
|
|
||||||
onAssignAttribute={handleAttributeAssignSuccess}
|
|
||||||
onUnassignAttribute={handleAttributeUnassignSuccess}
|
|
||||||
onProductTypeDelete={handleProductTypeDeleteSuccess}
|
|
||||||
onProductTypeUpdate={handleProductTypeUpdateSuccess}
|
|
||||||
onProductTypeAttributeReorder={() => undefined}
|
|
||||||
>
|
|
||||||
{({
|
|
||||||
assignAttribute,
|
|
||||||
deleteProductType,
|
|
||||||
unassignAttribute,
|
|
||||||
updateProductType,
|
|
||||||
reorderAttribute
|
|
||||||
}) => {
|
|
||||||
const handleProductTypeDelete = () =>
|
|
||||||
deleteProductType.mutate({ id });
|
|
||||||
const handleProductTypeUpdate = (
|
|
||||||
formData: ProductTypeForm
|
|
||||||
) => {
|
|
||||||
updateProductType.mutate({
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
hasVariants: formData.hasVariants,
|
|
||||||
isShippingRequired: formData.isShippingRequired,
|
|
||||||
name: formData.name,
|
|
||||||
productAttributes: formData.productAttributes.map(
|
|
||||||
choice => choice.value
|
|
||||||
),
|
|
||||||
taxCode: formData.taxType,
|
|
||||||
variantAttributes: formData.variantAttributes.map(
|
|
||||||
choice => choice.value
|
|
||||||
),
|
|
||||||
weight: formData.weight
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const handleProductTypeVariantsToggle = (
|
|
||||||
hasVariants: boolean
|
|
||||||
) =>
|
|
||||||
updateProductType.mutate({
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
hasVariants
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const handleAssignAttribute = () =>
|
|
||||||
assignAttribute.mutate({
|
|
||||||
id,
|
|
||||||
operations: params.ids.map(id => ({
|
|
||||||
id,
|
|
||||||
type: AttributeTypeEnum[params.type]
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleAttributeUnassign = () =>
|
const handleAttributeReorder = (
|
||||||
unassignAttribute.mutate({
|
event: ReorderEvent,
|
||||||
id,
|
type: AttributeTypeEnum
|
||||||
ids: [params.id]
|
) => {
|
||||||
});
|
const attributes =
|
||||||
|
type === AttributeTypeEnum.PRODUCT
|
||||||
|
? data.productType.productAttributes
|
||||||
|
: data.productType.variantAttributes;
|
||||||
|
|
||||||
const handleBulkAttributeUnassign = () =>
|
reorderAttribute.mutate({
|
||||||
unassignAttribute.mutate({
|
move: {
|
||||||
id,
|
id: attributes[event.oldIndex].id,
|
||||||
ids: params.ids
|
sortOrder: event.newIndex - event.oldIndex
|
||||||
});
|
},
|
||||||
|
productTypeId: id,
|
||||||
|
type
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const loading = updateProductType.opts.loading || dataLoading;
|
return (
|
||||||
|
<>
|
||||||
const handleAttributeReorder = (
|
<WindowTitle title={maybe(() => data.productType.name)} />
|
||||||
event: ReorderEvent,
|
<ProductTypeDetailsPage
|
||||||
type: AttributeTypeEnum
|
defaultWeightUnit={maybe(() => data.shop.defaultWeightUnit)}
|
||||||
) => {
|
disabled={loading}
|
||||||
const attributes =
|
errors={errors.formErrors}
|
||||||
type === AttributeTypeEnum.PRODUCT
|
pageTitle={maybe(() => data.productType.name)}
|
||||||
? data.productType.productAttributes
|
productType={maybe(() => data.productType)}
|
||||||
: data.productType.variantAttributes;
|
saveButtonBarState={updateProductTypeOpts.status}
|
||||||
|
taxTypes={maybe(() => data.taxTypes, [])}
|
||||||
reorderAttribute.mutate({
|
onAttributeAdd={type =>
|
||||||
move: {
|
navigate(
|
||||||
id: attributes[event.oldIndex].id,
|
productTypeUrl(id, {
|
||||||
sortOrder: event.newIndex - event.oldIndex
|
action: "assign-attribute",
|
||||||
},
|
type
|
||||||
productTypeId: id,
|
})
|
||||||
type
|
)
|
||||||
});
|
}
|
||||||
};
|
onAttributeClick={attributeId =>
|
||||||
|
navigate(attributeUrl(attributeId))
|
||||||
return (
|
}
|
||||||
<>
|
onAttributeReorder={handleAttributeReorder}
|
||||||
<WindowTitle title={maybe(() => data.productType.name)} />
|
onAttributeUnassign={attributeId =>
|
||||||
<ProductTypeDetailsPage
|
navigate(
|
||||||
defaultWeightUnit={maybe(
|
productTypeUrl(id, {
|
||||||
() => data.shop.defaultWeightUnit
|
action: "unassign-attribute",
|
||||||
)}
|
id: attributeId
|
||||||
disabled={loading}
|
})
|
||||||
errors={errors.formErrors}
|
)
|
||||||
pageTitle={maybe(() => data.productType.name)}
|
}
|
||||||
productType={maybe(() => data.productType)}
|
onBack={handleBack}
|
||||||
saveButtonBarState={updateProductType.opts.status}
|
onDelete={() =>
|
||||||
taxTypes={maybe(() => data.taxTypes, [])}
|
navigate(
|
||||||
onAttributeAdd={type =>
|
productTypeUrl(id, {
|
||||||
navigate(
|
action: "remove"
|
||||||
productTypeUrl(id, {
|
})
|
||||||
action: "assign-attribute",
|
)
|
||||||
type
|
}
|
||||||
})
|
onHasVariantsToggle={handleProductTypeVariantsToggle}
|
||||||
)
|
onSubmit={handleSubmit}
|
||||||
}
|
productAttributeList={{
|
||||||
onAttributeClick={attributeId =>
|
isChecked: productAttributeListActions.isSelected,
|
||||||
navigate(attributeUrl(attributeId))
|
selected: productAttributeListActions.listElements.length,
|
||||||
}
|
toggle: productAttributeListActions.toggle,
|
||||||
onAttributeReorder={handleAttributeReorder}
|
toggleAll: productAttributeListActions.toggleAll,
|
||||||
onAttributeUnassign={attributeId =>
|
toolbar: (
|
||||||
navigate(
|
<Button
|
||||||
productTypeUrl(id, {
|
color="primary"
|
||||||
action: "unassign-attribute",
|
onClick={() =>
|
||||||
id: attributeId
|
navigate(
|
||||||
})
|
productTypeUrl(id, {
|
||||||
)
|
action: "unassign-attributes",
|
||||||
}
|
ids: productAttributeListActions.listElements
|
||||||
onBack={handleBack}
|
})
|
||||||
onDelete={() =>
|
)
|
||||||
navigate(
|
}
|
||||||
productTypeUrl(id, {
|
>
|
||||||
action: "remove"
|
<FormattedMessage
|
||||||
})
|
defaultMessage="Unassign"
|
||||||
)
|
description="unassign attribute from product type, button"
|
||||||
}
|
|
||||||
onHasVariantsToggle={handleProductTypeVariantsToggle}
|
|
||||||
onSubmit={handleProductTypeUpdate}
|
|
||||||
productAttributeList={{
|
|
||||||
isChecked: productAttributeListActions.isSelected,
|
|
||||||
selected:
|
|
||||||
productAttributeListActions.listElements.length,
|
|
||||||
toggle: productAttributeListActions.toggle,
|
|
||||||
toggleAll: productAttributeListActions.toggleAll,
|
|
||||||
toolbar: (
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
productTypeUrl(id, {
|
|
||||||
action: "unassign-attributes",
|
|
||||||
ids:
|
|
||||||
productAttributeListActions.listElements
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Unassign"
|
|
||||||
description="unassign attribute from product type, button"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
variantAttributeList={{
|
|
||||||
isChecked: variantAttributeListActions.isSelected,
|
|
||||||
selected:
|
|
||||||
variantAttributeListActions.listElements.length,
|
|
||||||
toggle: variantAttributeListActions.toggle,
|
|
||||||
toggleAll: variantAttributeListActions.toggleAll,
|
|
||||||
toolbar: (
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
productTypeUrl(id, {
|
|
||||||
action: "unassign-attributes",
|
|
||||||
ids:
|
|
||||||
variantAttributeListActions.listElements
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Unassign"
|
|
||||||
description="unassign attribute from product type, button"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!dataLoading &&
|
|
||||||
Object.keys(AttributeTypeEnum).map(key => (
|
|
||||||
<AssignAttributeDialog
|
|
||||||
attributes={maybe(() =>
|
|
||||||
result.data.productType.availableAttributes.edges.map(
|
|
||||||
edge => edge.node
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
confirmButtonState={assignAttribute.opts.status}
|
|
||||||
errors={maybe(
|
|
||||||
() =>
|
|
||||||
assignAttribute.opts.data.attributeAssign.errors.map(
|
|
||||||
err => err.message
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
)}
|
|
||||||
loading={result.loading}
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleAssignAttribute}
|
|
||||||
onFetch={search}
|
|
||||||
onFetchMore={loadMore}
|
|
||||||
onOpen={result.refetch}
|
|
||||||
hasMore={maybe(
|
|
||||||
() =>
|
|
||||||
result.data.productType.availableAttributes
|
|
||||||
.pageInfo.hasNextPage,
|
|
||||||
false
|
|
||||||
)}
|
|
||||||
open={
|
|
||||||
params.action === "assign-attribute" &&
|
|
||||||
params.type === AttributeTypeEnum[key]
|
|
||||||
}
|
|
||||||
selected={maybe(() => params.ids, [])}
|
|
||||||
onToggle={attributeId => {
|
|
||||||
const ids = maybe(() => params.ids, []);
|
|
||||||
navigate(
|
|
||||||
productTypeUrl(id, {
|
|
||||||
...params,
|
|
||||||
ids: ids.includes(attributeId)
|
|
||||||
? params.ids.filter(
|
|
||||||
selectedId => selectedId !== attributeId
|
|
||||||
)
|
|
||||||
: [...ids, attributeId]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
key={key}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</Button>
|
||||||
<ProductTypeDeleteDialog
|
)
|
||||||
confirmButtonState={deleteProductType.opts.status}
|
}}
|
||||||
name={maybe(() => data.productType.name, "...")}
|
variantAttributeList={{
|
||||||
open={params.action === "remove"}
|
isChecked: variantAttributeListActions.isSelected,
|
||||||
onClose={() => navigate(productTypeUrl(id))}
|
selected: variantAttributeListActions.listElements.length,
|
||||||
onConfirm={handleProductTypeDelete}
|
toggle: variantAttributeListActions.toggle,
|
||||||
/>
|
toggleAll: variantAttributeListActions.toggleAll,
|
||||||
<ProductTypeBulkAttributeUnassignDialog
|
toolbar: (
|
||||||
attributeQuantity={maybe(() => params.ids.length)}
|
<Button
|
||||||
confirmButtonState={unassignAttribute.opts.status}
|
color="primary"
|
||||||
onClose={closeModal}
|
onClick={() =>
|
||||||
onConfirm={handleBulkAttributeUnassign}
|
navigate(
|
||||||
open={params.action === "unassign-attributes"}
|
productTypeUrl(id, {
|
||||||
productTypeName={maybe(
|
action: "unassign-attributes",
|
||||||
() => data.productType.name,
|
ids: variantAttributeListActions.listElements
|
||||||
"..."
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Unassign"
|
||||||
|
description="unassign attribute from product type, button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!dataLoading &&
|
||||||
|
Object.keys(AttributeTypeEnum).map(key => (
|
||||||
|
<AssignAttributeDialog
|
||||||
|
attributes={maybe(() =>
|
||||||
|
result.data.productType.availableAttributes.edges.map(
|
||||||
|
edge => edge.node
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
/>
|
confirmButtonState={assignAttribute.opts.status}
|
||||||
<ProductTypeAttributeUnassignDialog
|
errors={maybe(
|
||||||
attributeName={maybe(
|
|
||||||
() =>
|
() =>
|
||||||
[
|
assignAttribute.opts.data.attributeAssign.errors.map(
|
||||||
...data.productType.productAttributes,
|
err => err.message
|
||||||
...data.productType.variantAttributes
|
),
|
||||||
].find(attribute => attribute.id === params.id)
|
[]
|
||||||
.name,
|
|
||||||
"..."
|
|
||||||
)}
|
)}
|
||||||
confirmButtonState={unassignAttribute.opts.status}
|
loading={result.loading}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={handleAttributeUnassign}
|
onSubmit={handleAssignAttribute}
|
||||||
open={params.action === "unassign-attribute"}
|
onFetch={search}
|
||||||
productTypeName={maybe(
|
onFetchMore={loadMore}
|
||||||
() => data.productType.name,
|
onOpen={result.refetch}
|
||||||
"..."
|
hasMore={maybe(
|
||||||
|
() =>
|
||||||
|
result.data.productType.availableAttributes.pageInfo
|
||||||
|
.hasNextPage,
|
||||||
|
false
|
||||||
)}
|
)}
|
||||||
|
open={
|
||||||
|
params.action === "assign-attribute" &&
|
||||||
|
params.type === AttributeTypeEnum[key]
|
||||||
|
}
|
||||||
|
selected={maybe(() => params.ids, [])}
|
||||||
|
onToggle={attributeId => {
|
||||||
|
const ids = maybe(() => params.ids, []);
|
||||||
|
navigate(
|
||||||
|
productTypeUrl(id, {
|
||||||
|
...params,
|
||||||
|
ids: ids.includes(attributeId)
|
||||||
|
? params.ids.filter(
|
||||||
|
selectedId => selectedId !== attributeId
|
||||||
|
)
|
||||||
|
: [...ids, attributeId]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
key={key}
|
||||||
/>
|
/>
|
||||||
</>
|
))}
|
||||||
);
|
<ProductTypeDeleteDialog
|
||||||
}}
|
confirmButtonState={deleteProductType.opts.status}
|
||||||
</ProductTypeOperations>
|
name={maybe(() => data.productType.name, "...")}
|
||||||
);
|
open={params.action === "remove"}
|
||||||
}}
|
onClose={() => navigate(productTypeUrl(id))}
|
||||||
</TypedProductTypeDetailsQuery>
|
onConfirm={handleProductTypeDelete}
|
||||||
)}
|
/>
|
||||||
</ProductTypeUpdateErrors>
|
<ProductTypeBulkAttributeUnassignDialog
|
||||||
|
attributeQuantity={maybe(() => params.ids.length)}
|
||||||
|
confirmButtonState={unassignAttribute.opts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={handleBulkAttributeUnassign}
|
||||||
|
open={params.action === "unassign-attributes"}
|
||||||
|
productTypeName={maybe(() => data.productType.name, "...")}
|
||||||
|
/>
|
||||||
|
<ProductTypeAttributeUnassignDialog
|
||||||
|
attributeName={maybe(
|
||||||
|
() =>
|
||||||
|
[
|
||||||
|
...data.productType.productAttributes,
|
||||||
|
...data.productType.variantAttributes
|
||||||
|
].find(attribute => attribute.id === params.id).name,
|
||||||
|
"..."
|
||||||
|
)}
|
||||||
|
confirmButtonState={unassignAttribute.opts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={handleAttributeUnassign}
|
||||||
|
open={params.action === "unassign-attribute"}
|
||||||
|
productTypeName={maybe(() => data.productType.name, "...")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</ProductTypeOperations>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</TypedProductTypeDetailsQuery>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductTypeUpdate;
|
export default ProductTypeUpdate;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import SeoForm from "@saleor/components/SeoForm";
|
import SeoForm from "@saleor/components/SeoForm";
|
||||||
|
@ -20,6 +21,7 @@ import { SearchCollections_search_edges_node } from "@saleor/searches/types/Sear
|
||||||
import { FetchMoreProps, ListActions } from "@saleor/types";
|
import { FetchMoreProps, ListActions } from "@saleor/types";
|
||||||
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import { convertFromRaw, RawDraftContentState } from "draft-js";
|
import { convertFromRaw, RawDraftContentState } from "draft-js";
|
||||||
import { diff } from "fast-array-diff";
|
import { diff } from "fast-array-diff";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -155,6 +157,12 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
getChoices(maybe(() => product.collections, []))
|
getChoices(maybe(() => product.collections, []))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const initialData = getProductUpdatePageFormData(product, variants);
|
const initialData = getProductUpdatePageFormData(product, variants);
|
||||||
const initialDescription = maybe<RawDraftContentState>(() =>
|
const initialDescription = maybe<RawDraftContentState>(() =>
|
||||||
JSON.parse(product.descriptionJson)
|
JSON.parse(product.descriptionJson)
|
||||||
|
@ -167,11 +175,18 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
const hasVariants = maybe(() => product.productType.hasVariants, false);
|
const hasVariants = maybe(() => product.productType.hasVariants, false);
|
||||||
|
|
||||||
const handleSubmit = (data: ProductUpdatePageFormData) => {
|
const handleSubmit = (data: ProductUpdatePageFormData) => {
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (product.productType.hasVariants) {
|
if (product.productType.hasVariants) {
|
||||||
onSubmit({
|
onSubmit({
|
||||||
...data,
|
...data,
|
||||||
addStocks: [],
|
addStocks: [],
|
||||||
attributes,
|
attributes,
|
||||||
|
metadata,
|
||||||
|
privateMetadata,
|
||||||
removeStocks: [],
|
removeStocks: [],
|
||||||
updateStocks: []
|
updateStocks: []
|
||||||
});
|
});
|
||||||
|
@ -188,6 +203,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||||
),
|
),
|
||||||
attributes,
|
attributes,
|
||||||
|
metadata,
|
||||||
|
privateMetadata,
|
||||||
removeStocks: stockDiff.removed,
|
removeStocks: stockDiff.removed,
|
||||||
updateStocks: stocks.filter(
|
updateStocks: stocks.filter(
|
||||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||||
|
@ -224,6 +241,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
attributes,
|
attributes,
|
||||||
triggerChange
|
triggerChange
|
||||||
);
|
);
|
||||||
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -347,6 +365,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
"Add search engine title and description to make this product easier to find"
|
"Add search engine title and description to make this product easier to find"
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
<CardSpacer />
|
||||||
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ProductOrganization
|
<ProductOrganization
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
|
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||||
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
||||||
|
@ -17,6 +19,8 @@ import {
|
||||||
getAttributeInputFromVariant,
|
getAttributeInputFromVariant,
|
||||||
getStockInputFromVariant
|
getStockInputFromVariant
|
||||||
} from "@saleor/products/utils/data";
|
} from "@saleor/products/utils/data";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import { diff } from "fast-array-diff";
|
import { diff } from "fast-array-diff";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
@ -31,7 +35,7 @@ import ProductVariantImageSelectDialog from "../ProductVariantImageSelectDialog"
|
||||||
import ProductVariantNavigation from "../ProductVariantNavigation";
|
import ProductVariantNavigation from "../ProductVariantNavigation";
|
||||||
import ProductVariantPrice from "../ProductVariantPrice";
|
import ProductVariantPrice from "../ProductVariantPrice";
|
||||||
|
|
||||||
export interface ProductVariantPageFormData {
|
export interface ProductVariantPageFormData extends MetadataFormData {
|
||||||
costPrice: string;
|
costPrice: string;
|
||||||
price: string;
|
price: string;
|
||||||
sku: string;
|
sku: string;
|
||||||
|
@ -100,6 +104,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
const [isModalOpened, setModalStatus] = React.useState(false);
|
const [isModalOpened, setModalStatus] = React.useState(false);
|
||||||
const toggleModal = () => setModalStatus(!isModalOpened);
|
const toggleModal = () => setModalStatus(!isModalOpened);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const variantImages = maybe(() => variant.images.map(image => image.id), []);
|
const variantImages = maybe(() => variant.images.map(image => image.id), []);
|
||||||
const productImages = maybe(() =>
|
const productImages = maybe(() =>
|
||||||
variant.product.images.sort((prev, next) =>
|
variant.product.images.sort((prev, next) =>
|
||||||
|
@ -114,7 +124,9 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
|
|
||||||
const initialForm: ProductVariantPageFormData = {
|
const initialForm: ProductVariantPageFormData = {
|
||||||
costPrice: maybe(() => variant.costPrice.amount.toString(), ""),
|
costPrice: maybe(() => variant.costPrice.amount.toString(), ""),
|
||||||
|
metadata: variant?.metadata?.map(mapMetadataItemToInput),
|
||||||
price: maybe(() => variant.price.amount.toString(), ""),
|
price: maybe(() => variant.price.amount.toString(), ""),
|
||||||
|
privateMetadata: variant?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
sku: maybe(() => variant.sku, ""),
|
sku: maybe(() => variant.sku, ""),
|
||||||
trackInventory: variant?.trackInventory,
|
trackInventory: variant?.trackInventory,
|
||||||
weight: variant?.weight?.value.toString() || ""
|
weight: variant?.weight?.value.toString() || ""
|
||||||
|
@ -124,6 +136,10 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
const dataStocks = stocks.map(stock => stock.id);
|
const dataStocks = stocks.map(stock => stock.id);
|
||||||
const variantStocks = variant.stocks.map(stock => stock.warehouse.id);
|
const variantStocks = variant.stocks.map(stock => stock.warehouse.id);
|
||||||
const stockDiff = diff(variantStocks, dataStocks);
|
const stockDiff = diff(variantStocks, dataStocks);
|
||||||
|
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||||
|
const privateMetadata = isPrivateMetadataModified
|
||||||
|
? data.privateMetadata
|
||||||
|
: undefined;
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
...data,
|
...data,
|
||||||
|
@ -131,6 +147,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||||
),
|
),
|
||||||
attributes,
|
attributes,
|
||||||
|
metadata,
|
||||||
|
privateMetadata,
|
||||||
removeStocks: stockDiff.removed,
|
removeStocks: stockDiff.removed,
|
||||||
updateStocks: stocks.filter(
|
updateStocks: stocks.filter(
|
||||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||||
|
@ -152,6 +170,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
triggerChange();
|
triggerChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid variant="inverted">
|
<Grid variant="inverted">
|
||||||
|
@ -235,6 +255,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
removeStock(id);
|
removeStock(id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<CardSpacer />
|
||||||
|
<Metadata data={data} onChange={changeMetadata} />
|
||||||
</div>
|
</div>
|
||||||
</Grid>
|
</Grid>
|
||||||
<SaveButtonBar
|
<SaveButtonBar
|
||||||
|
|
|
@ -153,6 +153,13 @@ export const product: (
|
||||||
isFeatured: false,
|
isFeatured: false,
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
margin: { __typename: "Margin", start: 2, stop: 7 },
|
margin: { __typename: "Margin", start: 2, stop: 7 },
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "Ergonomic Plastic Bacon",
|
name: "Ergonomic Plastic Bacon",
|
||||||
pricing: {
|
pricing: {
|
||||||
__typename: "ProductPricingInfo",
|
__typename: "ProductPricingInfo",
|
||||||
|
@ -186,6 +193,7 @@ export const product: (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
privateMetadata: [],
|
||||||
productType: {
|
productType: {
|
||||||
__typename: "ProductType",
|
__typename: "ProductType",
|
||||||
hasVariants: true,
|
hasVariants: true,
|
||||||
|
@ -1516,12 +1524,20 @@ export const variant = (placeholderImage: string): ProductVariant => ({
|
||||||
url: placeholderImage
|
url: placeholderImage
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
metadata: [
|
||||||
|
{
|
||||||
|
__typename: "MetadataItem",
|
||||||
|
key: "integration.id",
|
||||||
|
value: "100023123"
|
||||||
|
}
|
||||||
|
],
|
||||||
name: "Extended Hard",
|
name: "Extended Hard",
|
||||||
price: {
|
price: {
|
||||||
__typename: "Money",
|
__typename: "Money",
|
||||||
amount: 100,
|
amount: 100,
|
||||||
currency: "USD"
|
currency: "USD"
|
||||||
},
|
},
|
||||||
|
privateMetadata: [],
|
||||||
product: {
|
product: {
|
||||||
__typename: "Product" as "Product",
|
__typename: "Product" as "Product",
|
||||||
id: "prod1",
|
id: "prod1",
|
||||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductCreate_productCreate_product_pricing {
|
||||||
priceRangeUndiscounted: ProductCreate_productCreate_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: ProductCreate_productCreate_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductCreate_productCreate_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductCreate_productCreate_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductCreate_productCreate_product_category {
|
export interface ProductCreate_productCreate_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -186,6 +198,8 @@ export interface ProductCreate_productCreate_product {
|
||||||
attributes: ProductCreate_productCreate_product_attributes[];
|
attributes: ProductCreate_productCreate_product_attributes[];
|
||||||
productType: ProductCreate_productCreate_product_productType;
|
productType: ProductCreate_productCreate_product_productType;
|
||||||
pricing: ProductCreate_productCreate_product_pricing | null;
|
pricing: ProductCreate_productCreate_product_pricing | null;
|
||||||
|
metadata: (ProductCreate_productCreate_product_metadata | null)[];
|
||||||
|
privateMetadata: (ProductCreate_productCreate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -93,6 +93,18 @@ export interface ProductDetails_product_pricing {
|
||||||
priceRangeUndiscounted: ProductDetails_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: ProductDetails_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductDetails_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductDetails_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductDetails_product_category {
|
export interface ProductDetails_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -180,6 +192,8 @@ export interface ProductDetails_product {
|
||||||
attributes: ProductDetails_product_attributes[];
|
attributes: ProductDetails_product_attributes[];
|
||||||
productType: ProductDetails_product_productType;
|
productType: ProductDetails_product_productType;
|
||||||
pricing: ProductDetails_product_pricing | null;
|
pricing: ProductDetails_product_pricing | null;
|
||||||
|
metadata: (ProductDetails_product_metadata | null)[];
|
||||||
|
privateMetadata: (ProductDetails_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductImageCreate_productImageCreate_product_pricing {
|
||||||
priceRangeUndiscounted: ProductImageCreate_productImageCreate_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: ProductImageCreate_productImageCreate_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductImageCreate_productImageCreate_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductImageCreate_productImageCreate_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductImageCreate_productImageCreate_product_category {
|
export interface ProductImageCreate_productImageCreate_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -186,6 +198,8 @@ export interface ProductImageCreate_productImageCreate_product {
|
||||||
attributes: ProductImageCreate_productImageCreate_product_attributes[];
|
attributes: ProductImageCreate_productImageCreate_product_attributes[];
|
||||||
productType: ProductImageCreate_productImageCreate_product_productType;
|
productType: ProductImageCreate_productImageCreate_product_productType;
|
||||||
pricing: ProductImageCreate_productImageCreate_product_pricing | null;
|
pricing: ProductImageCreate_productImageCreate_product_pricing | null;
|
||||||
|
metadata: (ProductImageCreate_productImageCreate_product_metadata | null)[];
|
||||||
|
privateMetadata: (ProductImageCreate_productImageCreate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductImageUpdate_productImageUpdate_product_pricing {
|
||||||
priceRangeUndiscounted: ProductImageUpdate_productImageUpdate_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: ProductImageUpdate_productImageUpdate_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductImageUpdate_productImageUpdate_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductImageUpdate_productImageUpdate_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductImageUpdate_productImageUpdate_product_category {
|
export interface ProductImageUpdate_productImageUpdate_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -186,6 +198,8 @@ export interface ProductImageUpdate_productImageUpdate_product {
|
||||||
attributes: ProductImageUpdate_productImageUpdate_product_attributes[];
|
attributes: ProductImageUpdate_productImageUpdate_product_attributes[];
|
||||||
productType: ProductImageUpdate_productImageUpdate_product_productType;
|
productType: ProductImageUpdate_productImageUpdate_product_productType;
|
||||||
pricing: ProductImageUpdate_productImageUpdate_product_pricing | null;
|
pricing: ProductImageUpdate_productImageUpdate_product_pricing | null;
|
||||||
|
metadata: (ProductImageUpdate_productImageUpdate_product_metadata | null)[];
|
||||||
|
privateMetadata: (ProductImageUpdate_productImageUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductUpdate_productUpdate_product_pricing {
|
||||||
priceRangeUndiscounted: ProductUpdate_productUpdate_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: ProductUpdate_productUpdate_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductUpdate_productUpdate_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductUpdate_productUpdate_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductUpdate_productUpdate_product_category {
|
export interface ProductUpdate_productUpdate_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -186,6 +198,8 @@ export interface ProductUpdate_productUpdate_product {
|
||||||
attributes: ProductUpdate_productUpdate_product_attributes[];
|
attributes: ProductUpdate_productUpdate_product_attributes[];
|
||||||
productType: ProductUpdate_productUpdate_product_productType;
|
productType: ProductUpdate_productUpdate_product_productType;
|
||||||
pricing: ProductUpdate_productUpdate_product_pricing | null;
|
pricing: ProductUpdate_productUpdate_product_pricing | null;
|
||||||
|
metadata: (ProductUpdate_productUpdate_product_metadata | null)[];
|
||||||
|
privateMetadata: (ProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
|
|
@ -8,6 +8,18 @@ import { WeightUnitsEnum } from "./../../types/globalTypes";
|
||||||
// GraphQL query operation: ProductVariantDetails
|
// GraphQL query operation: ProductVariantDetails
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
|
export interface ProductVariantDetails_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductVariantDetails_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductVariantDetails_productVariant_attributes_attribute_values {
|
export interface ProductVariantDetails_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -114,6 +126,8 @@ export interface ProductVariantDetails_productVariant_weight {
|
||||||
export interface ProductVariantDetails_productVariant {
|
export interface ProductVariantDetails_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (ProductVariantDetails_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (ProductVariantDetails_productVariant_privateMetadata | null)[];
|
||||||
attributes: ProductVariantDetails_productVariant_attributes[];
|
attributes: ProductVariantDetails_productVariant_attributes[];
|
||||||
costPrice: ProductVariantDetails_productVariant_costPrice | null;
|
costPrice: ProductVariantDetails_productVariant_costPrice | null;
|
||||||
images: (ProductVariantDetails_productVariant_images | null)[] | null;
|
images: (ProductVariantDetails_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -99,6 +99,18 @@ export interface SimpleProductUpdate_productUpdate_product_pricing {
|
||||||
priceRangeUndiscounted: SimpleProductUpdate_productUpdate_product_pricing_priceRangeUndiscounted | null;
|
priceRangeUndiscounted: SimpleProductUpdate_productUpdate_product_pricing_priceRangeUndiscounted | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productUpdate_product_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productUpdate_product_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimpleProductUpdate_productUpdate_product_category {
|
export interface SimpleProductUpdate_productUpdate_product_category {
|
||||||
__typename: "Category";
|
__typename: "Category";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -186,6 +198,8 @@ export interface SimpleProductUpdate_productUpdate_product {
|
||||||
attributes: SimpleProductUpdate_productUpdate_product_attributes[];
|
attributes: SimpleProductUpdate_productUpdate_product_attributes[];
|
||||||
productType: SimpleProductUpdate_productUpdate_product_productType;
|
productType: SimpleProductUpdate_productUpdate_product_productType;
|
||||||
pricing: SimpleProductUpdate_productUpdate_product_pricing | null;
|
pricing: SimpleProductUpdate_productUpdate_product_pricing | null;
|
||||||
|
metadata: (SimpleProductUpdate_productUpdate_product_metadata | null)[];
|
||||||
|
privateMetadata: (SimpleProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||||
name: string;
|
name: string;
|
||||||
descriptionJson: any;
|
descriptionJson: any;
|
||||||
seoTitle: string | null;
|
seoTitle: string | null;
|
||||||
|
@ -215,6 +229,18 @@ export interface SimpleProductUpdate_productVariantUpdate_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantUpdate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantUpdate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimpleProductUpdate_productVariantUpdate_productVariant_attributes_attribute_values {
|
export interface SimpleProductUpdate_productVariantUpdate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -321,6 +347,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_weight
|
||||||
export interface SimpleProductUpdate_productVariantUpdate_productVariant {
|
export interface SimpleProductUpdate_productVariantUpdate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (SimpleProductUpdate_productVariantUpdate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (SimpleProductUpdate_productVariantUpdate_productVariant_privateMetadata | null)[];
|
||||||
attributes: SimpleProductUpdate_productVariantUpdate_productVariant_attributes[];
|
attributes: SimpleProductUpdate_productVariantUpdate_productVariant_attributes[];
|
||||||
costPrice: SimpleProductUpdate_productVariantUpdate_productVariant_costPrice | null;
|
costPrice: SimpleProductUpdate_productVariantUpdate_productVariant_costPrice | null;
|
||||||
images: (SimpleProductUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
images: (SimpleProductUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
||||||
|
@ -346,6 +374,18 @@ export interface SimpleProductUpdate_productVariantStocksCreate_errors {
|
||||||
index: number | null;
|
index: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_attributes_attribute_values {
|
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -452,6 +492,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_w
|
||||||
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant {
|
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (SimpleProductUpdate_productVariantStocksCreate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (SimpleProductUpdate_productVariantStocksCreate_productVariant_privateMetadata | null)[];
|
||||||
attributes: SimpleProductUpdate_productVariantStocksCreate_productVariant_attributes[];
|
attributes: SimpleProductUpdate_productVariantStocksCreate_productVariant_attributes[];
|
||||||
costPrice: SimpleProductUpdate_productVariantStocksCreate_productVariant_costPrice | null;
|
costPrice: SimpleProductUpdate_productVariantStocksCreate_productVariant_costPrice | null;
|
||||||
images: (SimpleProductUpdate_productVariantStocksCreate_productVariant_images | null)[] | null;
|
images: (SimpleProductUpdate_productVariantStocksCreate_productVariant_images | null)[] | null;
|
||||||
|
@ -476,6 +518,18 @@ export interface SimpleProductUpdate_productVariantStocksDelete_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_attributes_attribute_values {
|
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -582,6 +636,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_w
|
||||||
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant {
|
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (SimpleProductUpdate_productVariantStocksDelete_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (SimpleProductUpdate_productVariantStocksDelete_productVariant_privateMetadata | null)[];
|
||||||
attributes: SimpleProductUpdate_productVariantStocksDelete_productVariant_attributes[];
|
attributes: SimpleProductUpdate_productVariantStocksDelete_productVariant_attributes[];
|
||||||
costPrice: SimpleProductUpdate_productVariantStocksDelete_productVariant_costPrice | null;
|
costPrice: SimpleProductUpdate_productVariantStocksDelete_productVariant_costPrice | null;
|
||||||
images: (SimpleProductUpdate_productVariantStocksDelete_productVariant_images | null)[] | null;
|
images: (SimpleProductUpdate_productVariantStocksDelete_productVariant_images | null)[] | null;
|
||||||
|
@ -607,6 +663,18 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_errors {
|
||||||
index: number | null;
|
index: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_attributes_attribute_values {
|
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -713,6 +781,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_w
|
||||||
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant {
|
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_privateMetadata | null)[];
|
||||||
attributes: SimpleProductUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
attributes: SimpleProductUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
||||||
costPrice: SimpleProductUpdate_productVariantStocksUpdate_productVariant_costPrice | null;
|
costPrice: SimpleProductUpdate_productVariantStocksUpdate_productVariant_costPrice | null;
|
||||||
images: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_images | null)[] | null;
|
images: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantCreate_productVariantCreate_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantCreate_productVariantCreate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantCreate_productVariantCreate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VariantCreate_productVariantCreate_productVariant_attributes_attribute_values {
|
export interface VariantCreate_productVariantCreate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -120,6 +132,8 @@ export interface VariantCreate_productVariantCreate_productVariant_weight {
|
||||||
export interface VariantCreate_productVariantCreate_productVariant {
|
export interface VariantCreate_productVariantCreate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (VariantCreate_productVariantCreate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (VariantCreate_productVariantCreate_productVariant_privateMetadata | null)[];
|
||||||
attributes: VariantCreate_productVariantCreate_productVariant_attributes[];
|
attributes: VariantCreate_productVariantCreate_productVariant_attributes[];
|
||||||
costPrice: VariantCreate_productVariantCreate_productVariant_costPrice | null;
|
costPrice: VariantCreate_productVariantCreate_productVariant_costPrice | null;
|
||||||
images: (VariantCreate_productVariantCreate_productVariant_images | null)[] | null;
|
images: (VariantCreate_productVariantCreate_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantImageAssign_variantImageAssign_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantImageAssign_variantImageAssign_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantImageAssign_variantImageAssign_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VariantImageAssign_variantImageAssign_productVariant_attributes_attribute_values {
|
export interface VariantImageAssign_variantImageAssign_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -120,6 +132,8 @@ export interface VariantImageAssign_variantImageAssign_productVariant_weight {
|
||||||
export interface VariantImageAssign_variantImageAssign_productVariant {
|
export interface VariantImageAssign_variantImageAssign_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (VariantImageAssign_variantImageAssign_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (VariantImageAssign_variantImageAssign_productVariant_privateMetadata | null)[];
|
||||||
attributes: VariantImageAssign_variantImageAssign_productVariant_attributes[];
|
attributes: VariantImageAssign_variantImageAssign_productVariant_attributes[];
|
||||||
costPrice: VariantImageAssign_variantImageAssign_productVariant_costPrice | null;
|
costPrice: VariantImageAssign_variantImageAssign_productVariant_costPrice | null;
|
||||||
images: (VariantImageAssign_variantImageAssign_productVariant_images | null)[] | null;
|
images: (VariantImageAssign_variantImageAssign_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantImageUnassign_variantImageUnassign_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantImageUnassign_variantImageUnassign_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantImageUnassign_variantImageUnassign_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VariantImageUnassign_variantImageUnassign_productVariant_attributes_attribute_values {
|
export interface VariantImageUnassign_variantImageUnassign_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -120,6 +132,8 @@ export interface VariantImageUnassign_variantImageUnassign_productVariant_weight
|
||||||
export interface VariantImageUnassign_variantImageUnassign_productVariant {
|
export interface VariantImageUnassign_variantImageUnassign_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (VariantImageUnassign_variantImageUnassign_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (VariantImageUnassign_variantImageUnassign_productVariant_privateMetadata | null)[];
|
||||||
attributes: VariantImageUnassign_variantImageUnassign_productVariant_attributes[];
|
attributes: VariantImageUnassign_variantImageUnassign_productVariant_attributes[];
|
||||||
costPrice: VariantImageUnassign_variantImageUnassign_productVariant_costPrice | null;
|
costPrice: VariantImageUnassign_variantImageUnassign_productVariant_costPrice | null;
|
||||||
images: (VariantImageUnassign_variantImageUnassign_productVariant_images | null)[] | null;
|
images: (VariantImageUnassign_variantImageUnassign_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantUpdate_productVariantUpdate_errors {
|
||||||
field: string | null;
|
field: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantUpdate_productVariantUpdate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantUpdate_productVariantUpdate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VariantUpdate_productVariantUpdate_productVariant_attributes_attribute_values {
|
export interface VariantUpdate_productVariantUpdate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -120,6 +132,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_weight {
|
||||||
export interface VariantUpdate_productVariantUpdate_productVariant {
|
export interface VariantUpdate_productVariantUpdate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (VariantUpdate_productVariantUpdate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (VariantUpdate_productVariantUpdate_productVariant_privateMetadata | null)[];
|
||||||
attributes: VariantUpdate_productVariantUpdate_productVariant_attributes[];
|
attributes: VariantUpdate_productVariantUpdate_productVariant_attributes[];
|
||||||
costPrice: VariantUpdate_productVariantUpdate_productVariant_costPrice | null;
|
costPrice: VariantUpdate_productVariantUpdate_productVariant_costPrice | null;
|
||||||
images: (VariantUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
images: (VariantUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
||||||
|
@ -145,6 +159,18 @@ export interface VariantUpdate_productVariantStocksUpdate_errors {
|
||||||
index: number | null;
|
index: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VariantUpdate_productVariantStocksUpdate_productVariant_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantUpdate_productVariantStocksUpdate_productVariant_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VariantUpdate_productVariantStocksUpdate_productVariant_attributes_attribute_values {
|
export interface VariantUpdate_productVariantStocksUpdate_productVariant_attributes_attribute_values {
|
||||||
__typename: "AttributeValue";
|
__typename: "AttributeValue";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -251,6 +277,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_weight
|
||||||
export interface VariantUpdate_productVariantStocksUpdate_productVariant {
|
export interface VariantUpdate_productVariantStocksUpdate_productVariant {
|
||||||
__typename: "ProductVariant";
|
__typename: "ProductVariant";
|
||||||
id: string;
|
id: string;
|
||||||
|
metadata: (VariantUpdate_productVariantStocksUpdate_productVariant_metadata | null)[];
|
||||||
|
privateMetadata: (VariantUpdate_productVariantStocksUpdate_productVariant_privateMetadata | null)[];
|
||||||
attributes: VariantUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
attributes: VariantUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
||||||
costPrice: VariantUpdate_productVariantStocksUpdate_productVariant_costPrice | null;
|
costPrice: VariantUpdate_productVariantStocksUpdate_productVariant_costPrice | null;
|
||||||
images: (VariantUpdate_productVariantStocksUpdate_productVariant_images | null)[] | null;
|
images: (VariantUpdate_productVariantStocksUpdate_productVariant_images | null)[] | null;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
||||||
|
@ -10,6 +11,7 @@ import {
|
||||||
} from "@saleor/products/types/ProductDetails";
|
} from "@saleor/products/types/ProductDetails";
|
||||||
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
|
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
|
||||||
import { StockInput } from "@saleor/types/globalTypes";
|
import { StockInput } from "@saleor/types/globalTypes";
|
||||||
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import { RawDraftContentState } from "draft-js";
|
import { RawDraftContentState } from "draft-js";
|
||||||
|
|
||||||
import { ProductAttributeInput } from "../components/ProductAttributes";
|
import { ProductAttributeInput } from "../components/ProductAttributes";
|
||||||
|
@ -168,7 +170,7 @@ export function getChoices(nodes: Node[]): SingleAutocompleteChoiceType[] {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductUpdatePageFormData {
|
export interface ProductUpdatePageFormData extends MetadataFormData {
|
||||||
basePrice: number;
|
basePrice: number;
|
||||||
category: string | null;
|
category: string | null;
|
||||||
collections: string[];
|
collections: string[];
|
||||||
|
@ -198,7 +200,9 @@ export function getProductUpdatePageFormData(
|
||||||
),
|
),
|
||||||
description: maybe(() => JSON.parse(product.descriptionJson)),
|
description: maybe(() => JSON.parse(product.descriptionJson)),
|
||||||
isPublished: maybe(() => product.isPublished, false),
|
isPublished: maybe(() => product.isPublished, false),
|
||||||
|
metadata: product?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: maybe(() => product.name, ""),
|
name: maybe(() => product.name, ""),
|
||||||
|
privateMetadata: product?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
publicationDate: maybe(() => product.publicationDate, ""),
|
publicationDate: maybe(() => product.publicationDate, ""),
|
||||||
seoDescription: maybe(() => product.seoDescription, ""),
|
seoDescription: maybe(() => product.seoDescription, ""),
|
||||||
seoTitle: maybe(() => product.seoTitle, ""),
|
seoTitle: maybe(() => product.seoTitle, ""),
|
||||||
|
|
|
@ -23,6 +23,11 @@ import {
|
||||||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
@ -81,6 +86,8 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const shop = useShop();
|
const shop = useShop();
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const { data, loading, refetch } = useProductDetails({
|
const { data, loading, refetch } = useProductDetails({
|
||||||
displayLoader: true,
|
displayLoader: true,
|
||||||
|
@ -181,10 +188,15 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
deleteProductImage({ variables: { id } });
|
deleteProductImage({ variables: { id } });
|
||||||
const handleImageEdit = (imageId: string) => () =>
|
const handleImageEdit = (imageId: string) => () =>
|
||||||
navigate(productImageUrl(id, imageId));
|
navigate(productImageUrl(id, imageId));
|
||||||
const handleSubmit = createUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
product,
|
product,
|
||||||
variables => updateProduct({ variables }),
|
createUpdateHandler(
|
||||||
variables => updateSimpleProduct({ variables })
|
product,
|
||||||
|
variables => updateProduct({ variables }),
|
||||||
|
variables => updateSimpleProduct({ variables })
|
||||||
|
),
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
);
|
);
|
||||||
const handleImageUpload = createImageUploadHandler(id, variables =>
|
const handleImageUpload = createImageUploadHandler(id, variables =>
|
||||||
createProductImage({ variables })
|
createProductImage({ variables })
|
||||||
|
|
|
@ -1,20 +1,34 @@
|
||||||
|
import { BulkStockErrorFragment } from "@saleor/fragments/types/BulkStockErrorFragment";
|
||||||
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
|
import { StockErrorFragment } from "@saleor/fragments/types/StockErrorFragment";
|
||||||
import { decimal, weight } from "@saleor/misc";
|
import { decimal, weight } from "@saleor/misc";
|
||||||
import { ProductUpdatePageSubmitData } from "@saleor/products/components/ProductUpdatePage";
|
import { ProductUpdatePageSubmitData } from "@saleor/products/components/ProductUpdatePage";
|
||||||
import { ProductDetails_product } from "@saleor/products/types/ProductDetails";
|
import { ProductDetails_product } from "@saleor/products/types/ProductDetails";
|
||||||
import { ProductImageCreateVariables } from "@saleor/products/types/ProductImageCreate";
|
import { ProductImageCreateVariables } from "@saleor/products/types/ProductImageCreate";
|
||||||
import { ProductImageReorderVariables } from "@saleor/products/types/ProductImageReorder";
|
import { ProductImageReorderVariables } from "@saleor/products/types/ProductImageReorder";
|
||||||
import { ProductUpdateVariables } from "@saleor/products/types/ProductUpdate";
|
import {
|
||||||
import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate";
|
ProductUpdate,
|
||||||
|
ProductUpdateVariables
|
||||||
|
} from "@saleor/products/types/ProductUpdate";
|
||||||
|
import {
|
||||||
|
SimpleProductUpdate,
|
||||||
|
SimpleProductUpdateVariables
|
||||||
|
} from "@saleor/products/types/SimpleProductUpdate";
|
||||||
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
|
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
|
import { MutationFetchResult } from "react-apollo";
|
||||||
import { arrayMove } from "react-sortable-hoc";
|
import { arrayMove } from "react-sortable-hoc";
|
||||||
|
|
||||||
export function createUpdateHandler(
|
export function createUpdateHandler(
|
||||||
product: ProductDetails_product,
|
product: ProductDetails_product,
|
||||||
updateProduct: (variables: ProductUpdateVariables) => void,
|
updateProduct: (
|
||||||
updateSimpleProduct: (variables: SimpleProductUpdateVariables) => void
|
variables: ProductUpdateVariables
|
||||||
|
) => Promise<MutationFetchResult<ProductUpdate>>,
|
||||||
|
updateSimpleProduct: (
|
||||||
|
variables: SimpleProductUpdateVariables
|
||||||
|
) => Promise<MutationFetchResult<SimpleProductUpdate>>
|
||||||
) {
|
) {
|
||||||
return (data: ProductUpdatePageSubmitData) => {
|
return async (data: ProductUpdatePageSubmitData) => {
|
||||||
const productVariables: ProductUpdateVariables = {
|
const productVariables: ProductUpdateVariables = {
|
||||||
attributes: data.attributes.map(attribute => ({
|
attributes: data.attributes.map(attribute => ({
|
||||||
id: attribute.id,
|
id: attribute.id,
|
||||||
|
@ -36,10 +50,15 @@ export function createUpdateHandler(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let errors: Array<
|
||||||
|
ProductErrorFragment | StockErrorFragment | BulkStockErrorFragment
|
||||||
|
>;
|
||||||
|
|
||||||
if (product.productType.hasVariants) {
|
if (product.productType.hasVariants) {
|
||||||
updateProduct(productVariables);
|
const result = await updateProduct(productVariables);
|
||||||
|
errors = result.data.productUpdate.errors;
|
||||||
} else {
|
} else {
|
||||||
updateSimpleProduct({
|
const result = await updateSimpleProduct({
|
||||||
...productVariables,
|
...productVariables,
|
||||||
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||||
deleteStocks: data.removeStocks,
|
deleteStocks: data.removeStocks,
|
||||||
|
@ -51,7 +70,16 @@ export function createUpdateHandler(
|
||||||
updateStocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
updateStocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
||||||
weight: weight(data.weight)
|
weight: weight(data.weight)
|
||||||
});
|
});
|
||||||
|
errors = [
|
||||||
|
...result.data.productUpdate.errors,
|
||||||
|
...result.data.productVariantStocksCreate.errors,
|
||||||
|
...result.data.productVariantStocksDelete.errors,
|
||||||
|
...result.data.productVariantStocksUpdate.errors,
|
||||||
|
...result.data.productVariantUpdate.errors
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,11 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
@ -67,6 +72,8 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
id: variantId
|
id: variantId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const [openModal] = createDialogActionHandlers<
|
const [openModal] = createDialogActionHandlers<
|
||||||
ProductVariantEditUrlDialog,
|
ProductVariantEditUrlDialog,
|
||||||
|
@ -140,6 +147,39 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async (data: ProductVariantPageSubmitData) => {
|
||||||
|
const result = await updateVariant({
|
||||||
|
variables: {
|
||||||
|
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||||
|
attributes: data.attributes.map(attribute => ({
|
||||||
|
id: attribute.id,
|
||||||
|
values: [attribute.value]
|
||||||
|
})),
|
||||||
|
costPrice: decimal(data.costPrice),
|
||||||
|
id: variantId,
|
||||||
|
price: decimal(data.price),
|
||||||
|
removeStocks: data.removeStocks,
|
||||||
|
sku: data.sku,
|
||||||
|
stocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
||||||
|
trackInventory: data.trackInventory,
|
||||||
|
weight: weight(data.weight)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [
|
||||||
|
...result.data?.productVariantStocksCreate.errors,
|
||||||
|
...result.data?.productVariantStocksDelete.errors,
|
||||||
|
...result.data?.productVariantStocksUpdate.errors,
|
||||||
|
...result.data?.productVariantUpdate.errors
|
||||||
|
];
|
||||||
|
};
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
data?.productVariant,
|
||||||
|
handleUpdate,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle title={data?.productVariant?.name} />
|
<WindowTitle title={data?.productVariant?.name} />
|
||||||
|
@ -158,25 +198,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
onDelete={() => openModal("remove")}
|
onDelete={() => openModal("remove")}
|
||||||
onImageSelect={handleImageSelect}
|
onImageSelect={handleImageSelect}
|
||||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
onSubmit={handleSubmit}
|
||||||
updateVariant({
|
|
||||||
variables: {
|
|
||||||
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
|
||||||
attributes: data.attributes.map(attribute => ({
|
|
||||||
id: attribute.id,
|
|
||||||
values: [attribute.value]
|
|
||||||
})),
|
|
||||||
costPrice: decimal(data.costPrice),
|
|
||||||
id: variantId,
|
|
||||||
price: decimal(data.price),
|
|
||||||
removeStocks: data.removeStocks,
|
|
||||||
sku: data.sku,
|
|
||||||
stocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
|
||||||
trackInventory: data.trackInventory,
|
|
||||||
weight: weight(data.weight)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onVariantClick={variantId => {
|
onVariantClick={variantId => {
|
||||||
navigate(productVariantEditUrl(productId, variantId));
|
navigate(productVariantEditUrl(productId, variantId));
|
||||||
}}
|
}}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -506,6 +506,12 @@ export enum MenuSortField {
|
||||||
NAME = "NAME",
|
NAME = "NAME",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MetadataErrorCode {
|
||||||
|
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||||
|
INVALID = "INVALID",
|
||||||
|
NOT_FOUND = "NOT_FOUND",
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrderAction {
|
export enum OrderAction {
|
||||||
CAPTURE = "CAPTURE",
|
CAPTURE = "CAPTURE",
|
||||||
MARK_AS_PAID = "MARK_AS_PAID",
|
MARK_AS_PAID = "MARK_AS_PAID",
|
||||||
|
@ -1186,6 +1192,11 @@ export interface MenuSortingInput {
|
||||||
field: MenuSortField;
|
field: MenuSortField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MetadataInput {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface NameTranslationInput {
|
export interface NameTranslationInput {
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
}
|
}
|
||||||
|
|
88
src/utils/handlers/metadataUpdateHandler.ts
Normal file
88
src/utils/handlers/metadataUpdateHandler.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
|
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||||
|
import { diff } from "fast-array-diff";
|
||||||
|
import { MutationFetchResult } from "react-apollo";
|
||||||
|
|
||||||
|
import {
|
||||||
|
UpdateMetadata,
|
||||||
|
UpdateMetadataVariables
|
||||||
|
} from "../metadata/types/UpdateMetadata";
|
||||||
|
import {
|
||||||
|
UpdatePrivateMetadata,
|
||||||
|
UpdatePrivateMetadataVariables
|
||||||
|
} from "../metadata/types/UpdatePrivateMetadata";
|
||||||
|
|
||||||
|
interface ObjectWithMetadata {
|
||||||
|
id: string;
|
||||||
|
metadata: MetadataInput[];
|
||||||
|
privateMetadata: MetadataInput[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMetadataUpdateHandler<TData extends MetadataFormData, TError>(
|
||||||
|
initial: ObjectWithMetadata,
|
||||||
|
update: (data: TData) => Promise<TError[]>,
|
||||||
|
updateMetadata: (
|
||||||
|
variables: UpdateMetadataVariables
|
||||||
|
) => Promise<MutationFetchResult<UpdateMetadata>>,
|
||||||
|
updatePrivateMetadata: (
|
||||||
|
variables: UpdatePrivateMetadataVariables
|
||||||
|
) => Promise<MutationFetchResult<UpdatePrivateMetadata>>
|
||||||
|
) {
|
||||||
|
return async (data: TData) => {
|
||||||
|
const errors = await update(data);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length === 0) {
|
||||||
|
if (data.metadata) {
|
||||||
|
const metaDiff = diff(
|
||||||
|
initial.metadata,
|
||||||
|
data.metadata,
|
||||||
|
(a, b) => a.key === b.key
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateMetaResult = await updateMetadata({
|
||||||
|
id: initial.id,
|
||||||
|
input: data.metadata,
|
||||||
|
keysToDelete: metaDiff.removed.map(meta => meta.key)
|
||||||
|
});
|
||||||
|
const updateMetaErrors = [
|
||||||
|
...(updateMetaResult.data.deleteMetadata.errors || []),
|
||||||
|
...(updateMetaResult.data.updateMetadata.errors || [])
|
||||||
|
];
|
||||||
|
|
||||||
|
if (updateMetaErrors.length > 0) {
|
||||||
|
return updateMetaErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.privateMetadata) {
|
||||||
|
const privateMetaDiff = diff(
|
||||||
|
initial.privateMetadata,
|
||||||
|
data.privateMetadata,
|
||||||
|
(a, b) => a.key === b.key
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatePrivateMetaResult = await updatePrivateMetadata({
|
||||||
|
id: initial.id,
|
||||||
|
input: data.privateMetadata,
|
||||||
|
keysToDelete: privateMetaDiff.removed.map(meta => meta.key)
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatePrivateMetaErrors = [
|
||||||
|
...(updatePrivateMetaResult.data.deletePrivateMetadata.errors || []),
|
||||||
|
...(updatePrivateMetaResult.data.updatePrivateMetadata.errors || [])
|
||||||
|
];
|
||||||
|
|
||||||
|
if (updatePrivateMetaErrors.length > 0) {
|
||||||
|
return updatePrivateMetaErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createMetadataUpdateHandler;
|
|
@ -1,7 +1,9 @@
|
||||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||||
import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo";
|
import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo";
|
||||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
|
import { MetadataItem } from "@saleor/fragments/types/MetadataItem";
|
||||||
import { Node } from "@saleor/types";
|
import { Node } from "@saleor/types";
|
||||||
|
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||||
|
|
||||||
export function mapCountriesToChoices(
|
export function mapCountriesToChoices(
|
||||||
countries: ShopInfo_shop_countries[]
|
countries: ShopInfo_shop_countries[]
|
||||||
|
@ -20,3 +22,10 @@ export function mapNodeToChoice(
|
||||||
value: node.id
|
value: node.id
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapMetadataItemToInput(item: MetadataItem): MetadataInput {
|
||||||
|
return {
|
||||||
|
key: item.key,
|
||||||
|
value: item.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
62
src/utils/metadata/types/UpdateMetadata.ts
Normal file
62
src/utils/metadata/types/UpdateMetadata.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MetadataInput, MetadataErrorCode } from "./../../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: UpdateMetadata
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface UpdateMetadata_updateMetadata_errors {
|
||||||
|
__typename: "MetadataError";
|
||||||
|
code: MetadataErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_updateMetadata {
|
||||||
|
__typename: "UpdateMetadata";
|
||||||
|
errors: UpdateMetadata_updateMetadata_errors[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_deleteMetadata_errors {
|
||||||
|
__typename: "MetadataError";
|
||||||
|
code: MetadataErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_deleteMetadata_item_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_deleteMetadata_item_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_deleteMetadata_item {
|
||||||
|
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
|
||||||
|
metadata: (UpdateMetadata_deleteMetadata_item_metadata | null)[];
|
||||||
|
privateMetadata: (UpdateMetadata_deleteMetadata_item_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata_deleteMetadata {
|
||||||
|
__typename: "DeleteMetadata";
|
||||||
|
errors: UpdateMetadata_deleteMetadata_errors[];
|
||||||
|
item: UpdateMetadata_deleteMetadata_item | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadata {
|
||||||
|
updateMetadata: UpdateMetadata_updateMetadata | null;
|
||||||
|
deleteMetadata: UpdateMetadata_deleteMetadata | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateMetadataVariables {
|
||||||
|
id: string;
|
||||||
|
input: MetadataInput[];
|
||||||
|
keysToDelete: string[];
|
||||||
|
}
|
62
src/utils/metadata/types/UpdatePrivateMetadata.ts
Normal file
62
src/utils/metadata/types/UpdatePrivateMetadata.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MetadataInput, MetadataErrorCode } from "./../../../types/globalTypes";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: UpdatePrivateMetadata
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_updatePrivateMetadata_errors {
|
||||||
|
__typename: "MetadataError";
|
||||||
|
code: MetadataErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_updatePrivateMetadata {
|
||||||
|
__typename: "UpdatePrivateMetadata";
|
||||||
|
errors: UpdatePrivateMetadata_updatePrivateMetadata_errors[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_deletePrivateMetadata_errors {
|
||||||
|
__typename: "MetadataError";
|
||||||
|
code: MetadataErrorCode;
|
||||||
|
field: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_deletePrivateMetadata_item_metadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_deletePrivateMetadata_item_privateMetadata {
|
||||||
|
__typename: "MetadataItem";
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_deletePrivateMetadata_item {
|
||||||
|
__typename: "ServiceAccount" | "App" | "Product" | "ProductType" | "Attribute" | "Category" | "ProductVariant" | "DigitalContent" | "Collection" | "User" | "Checkout" | "Order" | "Fulfillment" | "Invoice";
|
||||||
|
metadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_metadata | null)[];
|
||||||
|
privateMetadata: (UpdatePrivateMetadata_deletePrivateMetadata_item_privateMetadata | null)[];
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata_deletePrivateMetadata {
|
||||||
|
__typename: "DeletePrivateMetadata";
|
||||||
|
errors: UpdatePrivateMetadata_deletePrivateMetadata_errors[];
|
||||||
|
item: UpdatePrivateMetadata_deletePrivateMetadata_item | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadata {
|
||||||
|
updatePrivateMetadata: UpdatePrivateMetadata_updatePrivateMetadata | null;
|
||||||
|
deletePrivateMetadata: UpdatePrivateMetadata_deletePrivateMetadata | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdatePrivateMetadataVariables {
|
||||||
|
id: string;
|
||||||
|
input: MetadataInput[];
|
||||||
|
keysToDelete: string[];
|
||||||
|
}
|
75
src/utils/metadata/updateMetadata.ts
Normal file
75
src/utils/metadata/updateMetadata.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { metadataErrorFragment } from "@saleor/fragments/errors";
|
||||||
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { metadataFragment } from "../../fragments/metadata";
|
||||||
|
import {
|
||||||
|
UpdateMetadata,
|
||||||
|
UpdateMetadataVariables
|
||||||
|
} from "./types/UpdateMetadata";
|
||||||
|
import {
|
||||||
|
UpdatePrivateMetadata,
|
||||||
|
UpdatePrivateMetadataVariables
|
||||||
|
} from "./types/UpdatePrivateMetadata";
|
||||||
|
|
||||||
|
const updateMetadata = gql`
|
||||||
|
${metadataFragment}
|
||||||
|
${metadataErrorFragment}
|
||||||
|
mutation UpdateMetadata(
|
||||||
|
$id: ID!
|
||||||
|
$input: [MetadataInput!]!
|
||||||
|
$keysToDelete: [String!]!
|
||||||
|
) {
|
||||||
|
updateMetadata(id: $id, input: $input) {
|
||||||
|
errors: metadataErrors {
|
||||||
|
...MetadataErrorFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteMetadata(id: $id, keys: $keysToDelete) {
|
||||||
|
errors: metadataErrors {
|
||||||
|
...MetadataErrorFragment
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
...MetadataFragment
|
||||||
|
... on Node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const useMetadataUpdate = makeMutation<
|
||||||
|
UpdateMetadata,
|
||||||
|
UpdateMetadataVariables
|
||||||
|
>(updateMetadata);
|
||||||
|
|
||||||
|
const updatePrivateMetadata = gql`
|
||||||
|
${metadataFragment}
|
||||||
|
${metadataErrorFragment}
|
||||||
|
mutation UpdatePrivateMetadata(
|
||||||
|
$id: ID!
|
||||||
|
$input: [MetadataInput!]!
|
||||||
|
$keysToDelete: [String!]!
|
||||||
|
) {
|
||||||
|
updatePrivateMetadata(id: $id, input: $input) {
|
||||||
|
errors: metadataErrors {
|
||||||
|
...MetadataErrorFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deletePrivateMetadata(id: $id, keys: $keysToDelete) {
|
||||||
|
errors: metadataErrors {
|
||||||
|
...MetadataErrorFragment
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
...MetadataFragment
|
||||||
|
... on Node {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export const usePrivateMetadataUpdate = makeMutation<
|
||||||
|
UpdatePrivateMetadata,
|
||||||
|
UpdatePrivateMetadataVariables
|
||||||
|
>(updatePrivateMetadata);
|
28
src/utils/metadata/useMetadataChangeTrigger.ts
Normal file
28
src/utils/metadata/useMetadataChangeTrigger.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { FormChange } from "@saleor/hooks/useForm";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
function useMetadataChangeTrigger() {
|
||||||
|
const [isMetadataModified, setMetadataModified] = useState(false);
|
||||||
|
const [isPrivateMetadataModified, setPrivateMetadataModified] = useState(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const makeChangeHandler: (
|
||||||
|
onChange: FormChange
|
||||||
|
) => FormChange = onChange => event => {
|
||||||
|
if (event.target.name === "metadata") {
|
||||||
|
setMetadataModified(true);
|
||||||
|
} else {
|
||||||
|
setPrivateMetadataModified(true);
|
||||||
|
}
|
||||||
|
onChange(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMetadataModified,
|
||||||
|
isPrivateMetadataModified,
|
||||||
|
makeChangeHandler
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMetadataChangeTrigger;
|
25
testUtils/wrapper.tsx
Normal file
25
testUtils/wrapper.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Provider as DateProvider } from "@saleor/components/Date/DateContext";
|
||||||
|
import { Locale, RawLocaleProvider } from "@saleor/components/Locale";
|
||||||
|
import ThemeProvider from "@saleor/components/Theme";
|
||||||
|
import { TimezoneProvider } from "@saleor/components/Timezone";
|
||||||
|
import React from "react";
|
||||||
|
import { IntlProvider } from "react-intl";
|
||||||
|
|
||||||
|
const Wrapper: React.FC = ({ children }) => (
|
||||||
|
<IntlProvider defaultLocale={Locale.EN} locale={Locale.EN}>
|
||||||
|
<RawLocaleProvider
|
||||||
|
value={{
|
||||||
|
locale: Locale.EN,
|
||||||
|
setLocale: () => undefined
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
|
||||||
|
<TimezoneProvider value="America/New_York">
|
||||||
|
<ThemeProvider isDefaultDark={false}>{children}</ThemeProvider>
|
||||||
|
</TimezoneProvider>
|
||||||
|
</DateProvider>
|
||||||
|
</RawLocaleProvider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Wrapper;
|
Loading…
Reference in a new issue