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
|
||||
- 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 metadata - #670 by @dominik-zeglen
|
||||
|
||||
## 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",
|
||||
"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": {
|
||||
"context": "money",
|
||||
"string": "to {money}"
|
||||
|
|
|
@ -4,6 +4,8 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
|||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
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 SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import {
|
||||
|
@ -15,6 +17,8 @@ import { sectionNames } from "@saleor/intl";
|
|||
import { maybe } from "@saleor/misc";
|
||||
import { ReorderAction } from "@saleor/types";
|
||||
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
import slugify from "slugify";
|
||||
|
@ -38,7 +42,7 @@ export interface AttributePageProps {
|
|||
onValueUpdate: (id: string) => void;
|
||||
}
|
||||
|
||||
export interface AttributePageFormData {
|
||||
export interface AttributePageFormData extends MetadataFormData {
|
||||
availableInGrid: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
inputType: AttributeInputTypeEnum;
|
||||
|
@ -65,6 +69,12 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
onValueUpdate
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const initialForm: AttributePageFormData =
|
||||
attribute === null
|
||||
? {
|
||||
|
@ -72,7 +82,9 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
filterableInDashboard: true,
|
||||
filterableInStorefront: true,
|
||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||
metadata: undefined,
|
||||
name: "",
|
||||
privateMetadata: undefined,
|
||||
slug: "",
|
||||
storefrontSearchPosition: "",
|
||||
valueRequired: true,
|
||||
|
@ -92,7 +104,11 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
() => attribute.inputType,
|
||||
AttributeInputTypeEnum.DROPDOWN
|
||||
),
|
||||
metadata: attribute?.metadata?.map(mapMetadataItemToInput),
|
||||
name: maybe(() => attribute.name, ""),
|
||||
privateMetadata: attribute?.privateMetadata?.map(
|
||||
mapMetadataItemToInput
|
||||
),
|
||||
slug: maybe(() => attribute.slug, ""),
|
||||
storefrontSearchPosition: maybe(
|
||||
() => attribute.storefrontSearchPosition.toString(),
|
||||
|
@ -102,15 +118,26 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
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({
|
||||
...data,
|
||||
metadata,
|
||||
privateMetadata,
|
||||
slug: data.slug || slugify(data.name).toLowerCase()
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form initial={initialForm} onSubmit={handleSubmit}>
|
||||
{({ change, data, submit }) => (
|
||||
{({ change, data, submit }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.attributes)}
|
||||
|
@ -143,6 +170,12 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
onValueReorder={onValueReorder}
|
||||
onValueUpdate={onValueUpdate}
|
||||
/>
|
||||
{!!attribute && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<AttributeProperties
|
||||
|
@ -161,7 +194,8 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
|||
onDelete={attribute === null ? undefined : onDelete}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,7 +14,15 @@ export const attribute: AttributeDetailsFragment = {
|
|||
filterableInStorefront: true,
|
||||
id: "UHJvZHVjdEF0dHJpYnV0ZTo5",
|
||||
inputType: AttributeInputTypeEnum.DROPDOWN,
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "Author",
|
||||
privateMetadata: [],
|
||||
slug: "author",
|
||||
storefrontSearchPosition: 2,
|
||||
valueRequired: true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { attributeDetailsFragment } from "@saleor/fragments/attributes";
|
||||
import { productErrorFragment } from "@saleor/fragments/errors";
|
||||
import { TypedMutation } from "@saleor/mutations";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import {
|
||||
|
@ -46,7 +46,7 @@ const attributeBulkDelete = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeBulkDeleteMutation = TypedMutation<
|
||||
export const useAttributeBulkDeleteMutation = makeMutation<
|
||||
AttributeBulkDelete,
|
||||
AttributeBulkDeleteVariables
|
||||
>(attributeBulkDelete);
|
||||
|
@ -61,7 +61,7 @@ const attributeDelete = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeDeleteMutation = TypedMutation<
|
||||
export const useAttributeDeleteMutation = makeMutation<
|
||||
AttributeDelete,
|
||||
AttributeDeleteVariables
|
||||
>(attributeDelete);
|
||||
|
@ -80,7 +80,7 @@ export const attributeUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeUpdateMutation = TypedMutation<
|
||||
export const useAttributeUpdateMutation = makeMutation<
|
||||
AttributeUpdate,
|
||||
AttributeUpdateVariables
|
||||
>(attributeUpdateMutation);
|
||||
|
@ -99,7 +99,7 @@ const attributeValueDelete = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeValueDeleteMutation = TypedMutation<
|
||||
export const useAttributeValueDeleteMutation = makeMutation<
|
||||
AttributeValueDelete,
|
||||
AttributeValueDeleteVariables
|
||||
>(attributeValueDelete);
|
||||
|
@ -118,7 +118,7 @@ export const attributeValueUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeValueUpdateMutation = TypedMutation<
|
||||
export const useAttributeValueUpdateMutation = makeMutation<
|
||||
AttributeValueUpdate,
|
||||
AttributeValueUpdateVariables
|
||||
>(attributeValueUpdateMutation);
|
||||
|
@ -137,7 +137,7 @@ export const attributeValueCreateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeValueCreateMutation = TypedMutation<
|
||||
export const useAttributeValueCreateMutation = makeMutation<
|
||||
AttributeValueCreate,
|
||||
AttributeValueCreateVariables
|
||||
>(attributeValueCreateMutation);
|
||||
|
@ -156,7 +156,7 @@ export const attributeCreateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeCreateMutation = TypedMutation<
|
||||
export const useAttributeCreateMutation = makeMutation<
|
||||
AttributeCreate,
|
||||
AttributeCreateVariables
|
||||
>(attributeCreateMutation);
|
||||
|
@ -177,7 +177,7 @@ const attributeValueReorderMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeValueReorderMutation = TypedMutation<
|
||||
export const useAttributeValueReorderMutation = makeMutation<
|
||||
AttributeValueReorder,
|
||||
AttributeValueReorderVariables
|
||||
>(attributeValueReorderMutation);
|
||||
|
|
|
@ -6,7 +6,6 @@ import { pageInfoFragment } from "@saleor/fragments/pageInfo";
|
|||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedQuery } from "../queries";
|
||||
import {
|
||||
AttributeDetails,
|
||||
AttributeDetailsVariables
|
||||
|
@ -21,7 +20,7 @@ const attributeDetails = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AttributeDetailsQuery = TypedQuery<
|
||||
export const useAttributeDetailsQuery = makeQuery<
|
||||
AttributeDetails,
|
||||
AttributeDetailsVariables
|
||||
>(attributeDetails);
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeCreateInput, AttributeInputTypeEnum, AttributeValueType, Produ
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeCreate_attributeCreate_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeCreate_attributeCreate_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeCreate_attributeCreate_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType } from "./../../types/global
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeDetails_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeDetails_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeDetails_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeUpdateInput, AttributeInputTypeEnum, AttributeValueType, Produ
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeUpdate_attributeUpdate_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeUpdate_attributeUpdate_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeUpdate_attributeUpdate_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType,
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeValueCreate_attributeValueCreate_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeValueCreate_attributeValueCreate_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeValueCreate_attributeValueCreate_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType, ProductErrorCode } from "./
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeValueDelete_attributeValueDelete_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeValueDelete_attributeValueDelete_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeValueDelete_attributeValueDelete_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeValueCreateInput, AttributeInputTypeEnum, AttributeValueType,
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeValueUpdate_attributeValueUpdate_attribute {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeValueUpdate_attributeValueUpdate_attribute_metadata | null)[];
|
||||
privateMetadata: (AttributeValueUpdate_attributeValueUpdate_attribute_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { maybe } from "@saleor/misc";
|
||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
import { ProductErrorCode } from "@saleor/types/globalTypes";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
|
@ -21,8 +21,7 @@ import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDia
|
|||
import AttributeValueEditDialog, {
|
||||
AttributeValueEditDialogFormData
|
||||
} from "../../components/AttributeValueEditDialog";
|
||||
import { AttributeCreateMutation } from "../../mutations";
|
||||
import { AttributeCreate } from "../../types/AttributeCreate";
|
||||
import { useAttributeCreateMutation } from "../../mutations";
|
||||
import {
|
||||
attributeAddUrl,
|
||||
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 [openModal, closeModal] = createDialogActionHandlers<
|
||||
|
@ -73,17 +86,6 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
setValues(remove(values[params.id], values, areValuesEqual));
|
||||
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) => {
|
||||
if (isSelected(input, values, areValuesEqual)) {
|
||||
setValueErrors([attributeValueAlreadyExistsError]);
|
||||
|
@ -104,12 +106,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
setValues(move(values[oldIndex], values, areValuesEqual, newIndex));
|
||||
|
||||
return (
|
||||
<AttributeCreateMutation onCompleted={handleCreate}>
|
||||
{(attributeCreate, attributeCreateOpts) => (
|
||||
<>
|
||||
<AttributePage
|
||||
attribute={null}
|
||||
disabled={false}
|
||||
disabled={attributeCreateOpts.loading}
|
||||
errors={attributeCreateOpts.data?.attributeCreate.errors || []}
|
||||
onBack={() => navigate(attributeListUrl())}
|
||||
onDelete={undefined}
|
||||
|
@ -166,13 +166,13 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
<AttributeValueDeleteDialog
|
||||
attributeName={undefined}
|
||||
open={params.action === "remove-value"}
|
||||
name={maybe(() => values[id].name, "...")}
|
||||
name={getStringOrPlaceholder(values[id].name)}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onConfirm={handleValueDelete}
|
||||
/>
|
||||
<AttributeValueEditDialog
|
||||
attributeValue={maybe(() => values[params.id])}
|
||||
attributeValue={values[params.id]}
|
||||
confirmButtonState="default"
|
||||
disabled={false}
|
||||
errors={valueErrors}
|
||||
|
@ -183,8 +183,6 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
|||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AttributeCreateMutation>
|
||||
);
|
||||
};
|
||||
AttributeDetails.displayName = "AttributeDetails";
|
||||
|
|
|
@ -5,29 +5,30 @@ import { maybe } from "@saleor/misc";
|
|||
import { ReorderEvent } from "@saleor/types";
|
||||
import { getProductErrorMessage } from "@saleor/utils/errors";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||
import { move } from "@saleor/utils/lists";
|
||||
import {
|
||||
useMetadataUpdate,
|
||||
usePrivateMetadataUpdate
|
||||
} from "@saleor/utils/metadata/updateMetadata";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import AttributeDeleteDialog from "../../components/AttributeDeleteDialog";
|
||||
import AttributePage from "../../components/AttributePage";
|
||||
import AttributePage, {
|
||||
AttributePageFormData
|
||||
} from "../../components/AttributePage";
|
||||
import AttributeValueDeleteDialog from "../../components/AttributeValueDeleteDialog";
|
||||
import AttributeValueEditDialog from "../../components/AttributeValueEditDialog";
|
||||
import {
|
||||
AttributeDeleteMutation,
|
||||
AttributeUpdateMutation,
|
||||
AttributeValueCreateMutation,
|
||||
AttributeValueDeleteMutation,
|
||||
AttributeValueReorderMutation,
|
||||
AttributeValueUpdateMutation
|
||||
useAttributeDeleteMutation,
|
||||
useAttributeUpdateMutation,
|
||||
useAttributeValueCreateMutation,
|
||||
useAttributeValueDeleteMutation,
|
||||
useAttributeValueReorderMutation,
|
||||
useAttributeValueUpdateMutation
|
||||
} from "../../mutations";
|
||||
import { AttributeDetailsQuery } 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 { useAttributeDetailsQuery } from "../../queries";
|
||||
import {
|
||||
attributeListUrl,
|
||||
attributeUrl,
|
||||
|
@ -44,13 +45,22 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const [openModal, closeModal] = createDialogActionHandlers<
|
||||
AttributeUrlDialog,
|
||||
AttributeUrlQueryParams
|
||||
>(navigate, params => attributeUrl(id, params), params);
|
||||
|
||||
const handleDelete = (data: AttributeDelete) => {
|
||||
const { data, loading } = useAttributeDetailsQuery({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
|
||||
const [attributeDelete, attributeDeleteOpts] = useAttributeDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeDelete.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -60,8 +70,14 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
});
|
||||
navigate(attributeListUrl());
|
||||
}
|
||||
};
|
||||
const handleValueDelete = (data: AttributeValueDelete) => {
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
attributeValueDelete,
|
||||
attributeValueDeleteOpts
|
||||
] = useAttributeValueDeleteMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeValueDelete.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -72,16 +88,14 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
});
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleUpdate = (data: AttributeUpdate) => {
|
||||
if (data.attributeUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleValueUpdate = (data: AttributeValueUpdate) => {
|
||||
});
|
||||
|
||||
const [
|
||||
attributeValueUpdate,
|
||||
attributeValueUpdateOpts
|
||||
] = useAttributeValueUpdateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeValueUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -89,8 +103,25 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
});
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleValueCreate = (data: AttributeValueCreate) => {
|
||||
}
|
||||
});
|
||||
|
||||
const [attributeUpdate, attributeUpdateOpts] = useAttributeUpdateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const [
|
||||
attributeValueCreate,
|
||||
attributeValueCreateOpts
|
||||
] = useAttributeValueCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeValueCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -101,8 +132,11 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
});
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
const handleValueReorderMutation = (data: AttributeValueReorder) => {
|
||||
}
|
||||
});
|
||||
|
||||
const [attributeValueReorder] = useAttributeValueReorderMutation({
|
||||
onCompleted: data => {
|
||||
if (data.attributeReorderValues.errors.length !== 0) {
|
||||
notify({
|
||||
status: "error",
|
||||
|
@ -112,33 +146,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<AttributeDetailsQuery variables={{ id }}>
|
||||
{({ data, loading }) => (
|
||||
<AttributeDeleteMutation onCompleted={handleDelete}>
|
||||
{(attributeDelete, attributeDeleteOpts) => (
|
||||
<AttributeValueDeleteMutation onCompleted={handleValueDelete}>
|
||||
{(attributeValueDelete, attributeValueDeleteOpts) => (
|
||||
<AttributeUpdateMutation onCompleted={handleUpdate}>
|
||||
{(attributeUpdate, attributeUpdateOpts) => (
|
||||
<AttributeValueUpdateMutation
|
||||
onCompleted={handleValueUpdate}
|
||||
>
|
||||
{(attributeValueUpdate, attributeValueUpdateOpts) => (
|
||||
<AttributeValueCreateMutation
|
||||
onCompleted={handleValueCreate}
|
||||
>
|
||||
{(attributeValueCreate, attributeValueCreateOpts) => (
|
||||
<AttributeValueReorderMutation
|
||||
onCompleted={handleValueReorderMutation}
|
||||
>
|
||||
{attributeValueReorder => {
|
||||
const handleValueReorder = ({
|
||||
newIndex,
|
||||
oldIndex
|
||||
}: ReorderEvent) =>
|
||||
const handleValueReorder = ({ newIndex, oldIndex }: ReorderEvent) =>
|
||||
attributeValueReorder({
|
||||
optimisticResponse: {
|
||||
attributeReorderValues: {
|
||||
|
@ -164,38 +175,41 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
}
|
||||
});
|
||||
|
||||
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 (
|
||||
<>
|
||||
<AttributePage
|
||||
attribute={maybe(() => data.attribute)}
|
||||
disabled={loading}
|
||||
errors={
|
||||
attributeUpdateOpts.data
|
||||
?.attributeUpdate.errors || []
|
||||
}
|
||||
onBack={() =>
|
||||
navigate(attributeListUrl())
|
||||
}
|
||||
errors={attributeUpdateOpts.data?.attributeUpdate.errors || []}
|
||||
onBack={() => navigate(attributeListUrl())}
|
||||
onDelete={() => openModal("remove")}
|
||||
onSubmit={data => {
|
||||
const input = {
|
||||
...data,
|
||||
inputType: undefined
|
||||
};
|
||||
|
||||
attributeUpdate({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
...input,
|
||||
storefrontSearchPosition: parseInt(
|
||||
input.storefrontSearchPosition,
|
||||
0
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
onValueAdd={() => openModal("add-value")}
|
||||
onValueDelete={id =>
|
||||
openModal("remove-value", {
|
||||
|
@ -208,22 +222,13 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
id
|
||||
})
|
||||
}
|
||||
saveButtonBarState={
|
||||
attributeUpdateOpts.status
|
||||
}
|
||||
values={maybe(
|
||||
() => data.attribute.values
|
||||
)}
|
||||
saveButtonBarState={attributeUpdateOpts.status}
|
||||
values={maybe(() => data.attribute.values)}
|
||||
/>
|
||||
<AttributeDeleteDialog
|
||||
open={params.action === "remove"}
|
||||
name={maybe(
|
||||
() => data.attribute.name,
|
||||
"..."
|
||||
)}
|
||||
confirmButtonState={
|
||||
attributeDeleteOpts.status
|
||||
}
|
||||
name={maybe(() => data.attribute.name, "...")}
|
||||
confirmButtonState={attributeDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
attributeDelete({
|
||||
|
@ -234,22 +239,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
}
|
||||
/>
|
||||
<AttributeValueDeleteDialog
|
||||
attributeName={maybe(
|
||||
() => data.attribute.name,
|
||||
"..."
|
||||
)}
|
||||
attributeName={maybe(() => data.attribute.name, "...")}
|
||||
open={params.action === "remove-value"}
|
||||
name={maybe(
|
||||
() =>
|
||||
data.attribute.values.find(
|
||||
value => params.id === value.id
|
||||
).name,
|
||||
data.attribute.values.find(value => params.id === value.id).name,
|
||||
"..."
|
||||
)}
|
||||
useName={true}
|
||||
confirmButtonState={
|
||||
attributeValueDeleteOpts.status
|
||||
}
|
||||
confirmButtonState={attributeValueDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
attributeValueDelete({
|
||||
|
@ -261,13 +259,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
/>
|
||||
<AttributeValueEditDialog
|
||||
attributeValue={null}
|
||||
confirmButtonState={
|
||||
attributeValueCreateOpts.status
|
||||
}
|
||||
confirmButtonState={attributeValueCreateOpts.status}
|
||||
disabled={loading}
|
||||
errors={
|
||||
attributeValueCreateOpts.data
|
||||
?.attributeValueCreate.errors || []
|
||||
attributeValueCreateOpts.data?.attributeValueCreate.errors || []
|
||||
}
|
||||
open={params.action === "add-value"}
|
||||
onClose={closeModal}
|
||||
|
@ -282,26 +277,20 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
/>
|
||||
<AttributeValueEditDialog
|
||||
attributeValue={maybe(() =>
|
||||
data.attribute.values.find(
|
||||
value => params.id === value.id
|
||||
)
|
||||
data.attribute.values.find(value => params.id === value.id)
|
||||
)}
|
||||
confirmButtonState={
|
||||
attributeValueUpdateOpts.status
|
||||
}
|
||||
confirmButtonState={attributeValueUpdateOpts.status}
|
||||
disabled={loading}
|
||||
errors={
|
||||
attributeValueUpdateOpts.data
|
||||
?.attributeValueUpdate.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,
|
||||
id: data.attribute.values.find(value => params.id === value.id)
|
||||
.id,
|
||||
input
|
||||
}
|
||||
})
|
||||
|
@ -309,21 +298,6 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
|||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</AttributeValueReorderMutation>
|
||||
)}
|
||||
</AttributeValueCreateMutation>
|
||||
)}
|
||||
</AttributeValueUpdateMutation>
|
||||
)}
|
||||
</AttributeUpdateMutation>
|
||||
)}
|
||||
</AttributeValueDeleteMutation>
|
||||
)}
|
||||
</AttributeDeleteMutation>
|
||||
)}
|
||||
</AttributeDetailsQuery>
|
||||
);
|
||||
};
|
||||
AttributeDetails.displayName = "AttributeDetails";
|
||||
|
||||
|
|
|
@ -32,9 +32,8 @@ import useBulkActions from "../../../hooks/useBulkActions";
|
|||
import { maybe } from "../../../misc";
|
||||
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
|
||||
import AttributeListPage from "../../components/AttributeListPage";
|
||||
import { AttributeBulkDeleteMutation } from "../../mutations";
|
||||
import { useAttributeBulkDeleteMutation } from "../../mutations";
|
||||
import { useAttributeListQuery } from "../../queries";
|
||||
import { AttributeBulkDelete } from "../../types/AttributeBulkDelete";
|
||||
import {
|
||||
attributeAddUrl,
|
||||
attributeListUrl,
|
||||
|
@ -72,6 +71,26 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
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 currentTab =
|
||||
|
@ -125,32 +144,13 @@ const AttributeList: React.FC<AttributeListProps> = ({ 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 currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||
|
||||
return (
|
||||
<AttributeBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||
{(attributeBulkDelete, attributeBulkDeleteOpts) => (
|
||||
<>
|
||||
<AttributeListPage
|
||||
attributes={maybe(() =>
|
||||
data.attributes.edges.map(edge => edge.node)
|
||||
)}
|
||||
attributes={maybe(() => data.attributes.edges.map(edge => edge.node))}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
disabled={loading || attributeBulkDeleteOpts.loading}
|
||||
|
@ -190,9 +190,7 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
/>
|
||||
<AttributeBulkDeleteDialog
|
||||
confirmButtonState={attributeBulkDeleteOpts.status}
|
||||
open={
|
||||
params.action === "remove" && maybe(() => params.ids.length > 0)
|
||||
}
|
||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||
onConfirm={() =>
|
||||
attributeBulkDelete({ variables: { ids: params.ids } })
|
||||
}
|
||||
|
@ -213,8 +211,6 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
|
|||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</AttributeBulkDeleteMutation>
|
||||
);
|
||||
};
|
||||
AttributeList.displayName = "AttributeList";
|
||||
|
|
|
@ -6,12 +6,16 @@ import CardTitle from "@saleor/components/CardTitle";
|
|||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import Container from "@saleor/components/Container";
|
||||
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 SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
@ -28,7 +32,7 @@ import {
|
|||
import CategoryBackground from "../CategoryBackground";
|
||||
import CategoryProducts from "../CategoryProducts";
|
||||
|
||||
export interface FormData {
|
||||
export interface FormData extends MetadataFormData {
|
||||
backgroundImageAlt: string;
|
||||
description: RawDraftContentState;
|
||||
name: string;
|
||||
|
@ -100,24 +104,51 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
toggleAll
|
||||
}: CategoryUpdatePageProps) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const initialData: FormData = category
|
||||
? {
|
||||
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
|
||||
description: maybe(() => JSON.parse(category.descriptionJson)),
|
||||
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
||||
name: category.name || "",
|
||||
privateMetadata: category?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
seoDescription: category.seoDescription || "",
|
||||
seoTitle: category.seoTitle || ""
|
||||
}
|
||||
: {
|
||||
backgroundImageAlt: "",
|
||||
description: "",
|
||||
metadata: undefined,
|
||||
name: "",
|
||||
privateMetadata: undefined,
|
||||
seoDescription: "",
|
||||
seoTitle: ""
|
||||
};
|
||||
|
||||
const handleSubmit = (data: FormData) => {
|
||||
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||
const privateMetadata = isPrivateMetadataModified
|
||||
? data.privateMetadata
|
||||
: undefined;
|
||||
|
||||
onSubmit({
|
||||
...data,
|
||||
metadata,
|
||||
privateMetadata
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Form onSubmit={onSubmit} initial={initialData} confirmLeave>
|
||||
{({ data, change, submit, hasChanged }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.categories)}
|
||||
|
@ -153,6 +184,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
disabled={disabled}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
<CardSpacer />
|
||||
<TabContainer>
|
||||
<CategoriesTab
|
||||
isActive={currentTab === CategoryPageTab.categories}
|
||||
|
@ -237,7 +270,8 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
|
|||
disabled={disabled || !hasChanged}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -105,11 +105,19 @@ export const category: (
|
|||
},
|
||||
descriptionJson: JSON.stringify(content),
|
||||
id: "Q2F0ZWdvcnk6NA==",
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "Coffees",
|
||||
parent: {
|
||||
__typename: "Category",
|
||||
id: "Q2F0ZWdvcnk6Mw=="
|
||||
},
|
||||
privateMetadata: [],
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [
|
||||
|
|
|
@ -8,6 +8,18 @@ import { CategoryInput, ProductErrorCode } from "./../../types/globalTypes";
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -22,6 +34,8 @@ export interface CategoryCreate_categoryCreate_category_parent {
|
|||
export interface CategoryCreate_categoryCreate_category {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
metadata: (CategoryCreate_categoryCreate_category_metadata | null)[];
|
||||
privateMetadata: (CategoryCreate_categoryCreate_category_privateMetadata | null)[];
|
||||
backgroundImage: CategoryCreate_categoryCreate_category_backgroundImage | null;
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -131,6 +143,8 @@ export interface CategoryDetails_category_products {
|
|||
export interface CategoryDetails_category {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
metadata: (CategoryDetails_category_metadata | null)[];
|
||||
privateMetadata: (CategoryDetails_category_privateMetadata | null)[];
|
||||
backgroundImage: CategoryDetails_category_backgroundImage | null;
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { CategoryInput, ProductErrorCode } from "./../../types/globalTypes";
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -22,6 +34,8 @@ export interface CategoryUpdate_categoryUpdate_category_parent {
|
|||
export interface CategoryUpdate_categoryUpdate_category {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
metadata: (CategoryUpdate_categoryUpdate_category_metadata | null)[];
|
||||
privateMetadata: (CategoryUpdate_categoryUpdate_category_privateMetadata | null)[];
|
||||
backgroundImage: CategoryUpdate_categoryUpdate_category_backgroundImage | null;
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
|
|
|
@ -12,6 +12,11 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
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 { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -22,7 +27,8 @@ import { productAddUrl, productUrl } from "../../products/urls";
|
|||
import { CategoryInput } from "../../types/globalTypes";
|
||||
import {
|
||||
CategoryPageTab,
|
||||
CategoryUpdatePage
|
||||
CategoryUpdatePage,
|
||||
FormData
|
||||
} from "../components/CategoryUpdatePage/CategoryUpdatePage";
|
||||
import {
|
||||
useCategoryBulkDeleteMutation,
|
||||
|
@ -63,6 +69,8 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
params.ids
|
||||
);
|
||||
const intl = useIntl();
|
||||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const paginationState = createPaginationState(PAGINATE_BY, params);
|
||||
const { data, loading, refetch } = useCategoryDetailsQuery({
|
||||
|
@ -167,6 +175,31 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
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 (
|
||||
<>
|
||||
<WindowTitle title={maybe(() => data.category.name)} />
|
||||
|
@ -209,22 +242,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
onPreviousPage={loadPreviousPage}
|
||||
pageInfo={pageInfo}
|
||||
onProductClick={id => () => navigate(productUrl(id))}
|
||||
onSubmit={formData =>
|
||||
updateCategory({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImageAlt: formData.backgroundImageAlt,
|
||||
descriptionJson: JSON.stringify(formData.description),
|
||||
name: formData.name,
|
||||
seo: {
|
||||
description: formData.seoDescription,
|
||||
title: formData.seoTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
products={maybe(() =>
|
||||
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 Grid from "@saleor/components/Grid";
|
||||
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 SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import SeoForm from "@saleor/components/SeoForm";
|
||||
|
@ -14,6 +16,8 @@ import VisibilityCard from "@saleor/components/VisibilityCard";
|
|||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||
import useDateLocalize from "@saleor/hooks/useDateLocalize";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -25,7 +29,7 @@ import CollectionDetails from "../CollectionDetails/CollectionDetails";
|
|||
import { CollectionImage } from "../CollectionImage/CollectionImage";
|
||||
import CollectionProducts from "../CollectionProducts/CollectionProducts";
|
||||
|
||||
export interface CollectionDetailsPageFormData {
|
||||
export interface CollectionDetailsPageFormData extends MetadataFormData {
|
||||
backgroundImageAlt: string;
|
||||
description: RawDraftContentState;
|
||||
name: string;
|
||||
|
@ -64,6 +68,24 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
}: CollectionDetailsPageProps) => {
|
||||
const intl = useIntl();
|
||||
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 (
|
||||
<Form
|
||||
|
@ -72,15 +94,22 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
description: maybe(() => JSON.parse(collection.descriptionJson)),
|
||||
isFeatured,
|
||||
isPublished: maybe(() => collection.isPublished, false),
|
||||
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
||||
name: maybe(() => collection.name, ""),
|
||||
privateMetadata: collection?.privateMetadata?.map(
|
||||
mapMetadataItemToInput
|
||||
),
|
||||
publicationDate: maybe(() => collection.publicationDate, ""),
|
||||
seoDescription: maybe(() => collection.seoDescription, ""),
|
||||
seoTitle: maybe(() => collection.seoTitle, "")
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
onSubmit={handleSubmit}
|
||||
confirmLeave
|
||||
>
|
||||
{({ change, data, hasChanged, submit }) => (
|
||||
{({ change, data, hasChanged, submit }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.collections)}
|
||||
|
@ -104,6 +133,8 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
onChange={change}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
<CardSpacer />
|
||||
<CollectionProducts
|
||||
disabled={disabled}
|
||||
collection={collection}
|
||||
|
@ -173,7 +204,8 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
|
|||
onSave={submit}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</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),
|
||||
id: "Q29sbGVjdGlvbjox",
|
||||
isPublished: true,
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "Summer collection",
|
||||
privateMetadata: [],
|
||||
products: {
|
||||
__typename: "ProductCountableConnection",
|
||||
edges: [
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
productErrorFragment,
|
||||
shopErrorFragment
|
||||
} from "@saleor/fragments/errors";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedMutation } from "../mutations";
|
||||
import {
|
||||
CollectionAssignProduct,
|
||||
CollectionAssignProductVariables
|
||||
|
@ -56,7 +56,7 @@ const collectionUpdate = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionUpdateMutation = TypedMutation<
|
||||
export const useCollectionUpdateMutation = makeMutation<
|
||||
CollectionUpdate,
|
||||
CollectionUpdateVariables
|
||||
>(collectionUpdate);
|
||||
|
@ -90,7 +90,7 @@ const collectionUpdateWithHomepage = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionUpdateWithHomepageMutation = TypedMutation<
|
||||
export const useCollectionUpdateWithHomepageMutation = makeMutation<
|
||||
CollectionUpdateWithHomepage,
|
||||
CollectionUpdateWithHomepageVariables
|
||||
>(collectionUpdateWithHomepage);
|
||||
|
@ -129,7 +129,7 @@ const assignCollectionProduct = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionAssignProductMutation = TypedMutation<
|
||||
export const useCollectionAssignProductMutation = makeMutation<
|
||||
CollectionAssignProduct,
|
||||
CollectionAssignProductVariables
|
||||
>(assignCollectionProduct);
|
||||
|
@ -148,7 +148,7 @@ const createCollection = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionCreateMutation = TypedMutation<
|
||||
export const useCollectionCreateMutation = makeMutation<
|
||||
CreateCollection,
|
||||
CreateCollectionVariables
|
||||
>(createCollection);
|
||||
|
@ -163,7 +163,7 @@ const removeCollection = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionRemoveMutation = TypedMutation<
|
||||
export const useCollectionRemoveMutation = makeMutation<
|
||||
RemoveCollection,
|
||||
RemoveCollectionVariables
|
||||
>(removeCollection);
|
||||
|
@ -213,7 +213,7 @@ const unassignCollectionProduct = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedUnassignCollectionProductMutation = TypedMutation<
|
||||
export const useUnassignCollectionProductMutation = makeMutation<
|
||||
UnassignCollectionProduct,
|
||||
UnassignCollectionProductVariables
|
||||
>(unassignCollectionProduct);
|
||||
|
@ -228,7 +228,7 @@ const collectionBulkDelete = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionBulkDelete = TypedMutation<
|
||||
export const useCollectionBulkDelete = makeMutation<
|
||||
CollectionBulkDelete,
|
||||
CollectionBulkDeleteVariables
|
||||
>(collectionBulkDelete);
|
||||
|
@ -243,7 +243,7 @@ const collectionBulkPublish = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedCollectionBulkPublish = TypedMutation<
|
||||
export const useCollectionBulkPublish = makeMutation<
|
||||
CollectionBulkPublish,
|
||||
CollectionBulkPublishVariables
|
||||
>(collectionBulkPublish);
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -56,6 +68,8 @@ export interface CollectionDetails_collection {
|
|||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
metadata: (CollectionDetails_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionDetails_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionDetails_collection_backgroundImage | null;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { CollectionInput, ProductErrorCode } from "./../../types/globalTypes";
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -19,6 +31,8 @@ export interface CollectionUpdate_collectionUpdate_collection {
|
|||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
metadata: (CollectionUpdate_collectionUpdate_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionUpdate_collectionUpdate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionUpdate_collectionUpdate_collection_backgroundImage | null;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
|
|
|
@ -30,6 +30,18 @@ export interface CollectionUpdateWithHomepage_homepageCollectionUpdate {
|
|||
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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -41,6 +53,8 @@ export interface CollectionUpdateWithHomepage_collectionUpdate_collection {
|
|||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
metadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_metadata | null)[];
|
||||
privateMetadata: (CollectionUpdateWithHomepage_collectionUpdate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CollectionUpdateWithHomepage_collectionUpdate_collection_backgroundImage | null;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { CollectionCreateInput, ProductErrorCode } from "./../../types/globalTyp
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -19,6 +31,8 @@ export interface CreateCollection_collectionCreate_collection {
|
|||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
metadata: (CreateCollection_collectionCreate_collection_metadata | null)[];
|
||||
privateMetadata: (CreateCollection_collectionCreate_collection_privateMetadata | null)[];
|
||||
backgroundImage: CreateCollection_collectionCreate_collection_backgroundImage | null;
|
||||
descriptionJson: any;
|
||||
publicationDate: any | null;
|
||||
|
|
|
@ -7,8 +7,7 @@ import { useIntl } from "react-intl";
|
|||
|
||||
import { CollectionCreateInput } from "../../types/globalTypes";
|
||||
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
|
||||
import { TypedCollectionCreateMutation } from "../mutations";
|
||||
import { CreateCollection } from "../types/CreateCollection";
|
||||
import { useCollectionCreateMutation } from "../mutations";
|
||||
import { collectionListUrl, collectionUrl } from "../urls";
|
||||
|
||||
export const CollectionCreate: React.FC = () => {
|
||||
|
@ -16,7 +15,8 @@ export const CollectionCreate: React.FC = () => {
|
|||
const notify = useNotifier();
|
||||
const intl = useIntl();
|
||||
|
||||
const handleCollectionCreateSuccess = (data: CreateCollection) => {
|
||||
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.collectionCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -35,10 +35,10 @@ export const CollectionCreate: React.FC = () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedCollectionCreateMutation onCompleted={handleCollectionCreateSuccess}>
|
||||
{(createCollection, createCollectionOpts) => (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
|
@ -70,8 +70,6 @@ export const CollectionCreate: React.FC = () => {
|
|||
saveButtonBarState={createCollectionOpts.status}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TypedCollectionCreateMutation>
|
||||
);
|
||||
};
|
||||
export default CollectionCreate;
|
||||
|
|
|
@ -14,6 +14,11 @@ import usePaginator, {
|
|||
import { commonMessages } from "@saleor/intl";
|
||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||
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 { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -23,13 +28,15 @@ import { CollectionInput } from "../../types/globalTypes";
|
|||
import CollectionDetailsPage, {
|
||||
CollectionDetailsPageFormData
|
||||
} from "../components/CollectionDetailsPage/CollectionDetailsPage";
|
||||
import CollectionOperations from "../containers/CollectionOperations";
|
||||
import {
|
||||
useCollectionAssignProductMutation,
|
||||
useCollectionRemoveMutation,
|
||||
useCollectionUpdateMutation,
|
||||
useCollectionUpdateWithHomepageMutation,
|
||||
useUnassignCollectionProductMutation
|
||||
} from "../mutations";
|
||||
import { TypedCollectionDetailsQuery } from "../queries";
|
||||
import { CollectionAssignProduct } from "../types/CollectionAssignProduct";
|
||||
import { CollectionUpdate } from "../types/CollectionUpdate";
|
||||
import { CollectionUpdateWithHomepage } from "../types/CollectionUpdateWithHomepage";
|
||||
import { RemoveCollection } from "../types/RemoveCollection";
|
||||
import { UnassignCollectionProduct } from "../types/UnassignCollectionProduct";
|
||||
import {
|
||||
collectionListUrl,
|
||||
collectionUrl,
|
||||
|
@ -56,6 +63,90 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
const { search, result } = useProductSearch({
|
||||
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<
|
||||
CollectionUrlDialog,
|
||||
|
@ -76,86 +167,8 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
if (collection === null) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
}
|
||||
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 handleCollectioUpdateWithHomepage = (
|
||||
data: CollectionUpdateWithHomepage
|
||||
) => {
|
||||
if (data.homepageCollectionUpdate.errors.length === 0) {
|
||||
handleCollectionUpdate(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProductAssign = (data: CollectionAssignProduct) => {
|
||||
if (data.collectionAddProducts.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Added product to collection"
|
||||
})
|
||||
});
|
||||
navigate(collectionUrl(id), true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProductUnassign = (data: UnassignCollectionProduct) => {
|
||||
if (data.collectionRemoveProducts.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted product from collection"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCollectionRemove = (data: RemoveCollection) => {
|
||||
if (data.collectionDelete.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Deleted collection"
|
||||
})
|
||||
});
|
||||
navigate(collectionListUrl());
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CollectionOperations
|
||||
onUpdate={handleCollectionUpdate}
|
||||
onUpdateWithCollection={handleCollectioUpdateWithHomepage}
|
||||
onProductAssign={handleProductAssign}
|
||||
onProductUnassign={handleProductUnassign}
|
||||
onRemove={handleCollectionRemove}
|
||||
>
|
||||
{({
|
||||
updateCollection,
|
||||
updateCollectionWithHomepage,
|
||||
assignProduct,
|
||||
unassignProduct,
|
||||
removeCollection
|
||||
}) => {
|
||||
const handleSubmit = (
|
||||
const handleUpdate = async (
|
||||
formData: CollectionDetailsPageFormData
|
||||
) => {
|
||||
const input: CollectionInput = {
|
||||
|
@ -174,39 +187,47 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
: false;
|
||||
|
||||
if (formData.isFeatured !== isFeatured) {
|
||||
updateCollectionWithHomepage.mutate({
|
||||
const result = await updateCollectionWithHomepage({
|
||||
variables: {
|
||||
homepageId: formData.isFeatured ? id : null,
|
||||
id,
|
||||
input
|
||||
}
|
||||
});
|
||||
return [
|
||||
...result.data.collectionUpdate.errors,
|
||||
...result.data.homepageCollectionUpdate.errors
|
||||
];
|
||||
} else {
|
||||
updateCollection.mutate({
|
||||
const result = await updateCollection({
|
||||
variables: {
|
||||
id,
|
||||
input
|
||||
}
|
||||
});
|
||||
|
||||
return result.data.collectionUpdate.errors;
|
||||
}
|
||||
};
|
||||
const handleSubmit = createMetadataUpdateHandler(
|
||||
data?.collection,
|
||||
handleUpdate,
|
||||
variables => updateMetadata({ variables }),
|
||||
variables => updatePrivateMetadata({ variables })
|
||||
);
|
||||
|
||||
const formTransitionState = getMutationState(
|
||||
updateCollection.opts.called ||
|
||||
updateCollectionWithHomepage.opts.called,
|
||||
updateCollection.opts.loading ||
|
||||
updateCollectionWithHomepage.opts.loading,
|
||||
maybe(() => updateCollection.opts.data.collectionUpdate.errors),
|
||||
maybe(
|
||||
() =>
|
||||
updateCollectionWithHomepage.opts.data.collectionUpdate
|
||||
.errors
|
||||
),
|
||||
maybe(
|
||||
() =>
|
||||
updateCollectionWithHomepage.opts.data
|
||||
.homepageCollectionUpdate.errors
|
||||
)
|
||||
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(
|
||||
maybe(() => data.collection.products.pageInfo),
|
||||
data?.collection?.products?.pageInfo,
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
@ -219,22 +240,21 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
onBack={handleBack}
|
||||
disabled={loading}
|
||||
collection={data?.collection}
|
||||
errors={
|
||||
updateCollection.opts?.data?.collectionUpdate.errors || []
|
||||
}
|
||||
errors={updateCollectionOpts?.data?.collectionUpdate.errors || []}
|
||||
isFeatured={maybe(
|
||||
() =>
|
||||
data.shop.homepageCollection.id === data.collection.id,
|
||||
() => data.shop.homepageCollection.id === data.collection.id,
|
||||
false
|
||||
)}
|
||||
onCollectionRemove={() => openModal("remove")}
|
||||
onImageDelete={() => openModal("removeImage")}
|
||||
onImageUpload={file =>
|
||||
updateCollection.mutate({
|
||||
updateCollection({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: file
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -243,10 +263,12 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
pageInfo={pageInfo}
|
||||
onProductUnassign={(productId, event) => {
|
||||
event.stopPropagation();
|
||||
unassignProduct.mutate({
|
||||
unassignProduct({
|
||||
variables: {
|
||||
collectionId: id,
|
||||
productIds: [productId],
|
||||
...paginationState
|
||||
}
|
||||
});
|
||||
}}
|
||||
onRowClick={id => () => navigate(productUrl(id))}
|
||||
|
@ -272,16 +294,18 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
toggleAll={toggleAll}
|
||||
/>
|
||||
<AssignProductDialog
|
||||
confirmButtonState={assignProduct.opts.status}
|
||||
confirmButtonState={assignProductOpts.status}
|
||||
open={params.action === "assign"}
|
||||
onFetch={search}
|
||||
loading={result.loading}
|
||||
onClose={closeModal}
|
||||
onSubmit={products =>
|
||||
assignProduct.mutate({
|
||||
assignProduct({
|
||||
variables: {
|
||||
...paginationState,
|
||||
collectionId: id,
|
||||
productIds: products.map(product => product.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
products={maybe(() =>
|
||||
|
@ -291,9 +315,13 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
)}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={removeCollection.opts.status}
|
||||
confirmButtonState={removeCollectionOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() => removeCollection.mutate({ id })}
|
||||
onConfirm={() =>
|
||||
removeCollection({
|
||||
variables: { id }
|
||||
})
|
||||
}
|
||||
open={params.action === "remove"}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Collection",
|
||||
|
@ -315,13 +343,15 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
confirmButtonState={unassignProduct.opts.status}
|
||||
confirmButtonState={unassignProductOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
unassignProduct.mutate({
|
||||
unassignProduct({
|
||||
variables: {
|
||||
...paginationState,
|
||||
collectionId: id,
|
||||
productIds: params.ids
|
||||
}
|
||||
})
|
||||
}
|
||||
open={params.action === "unassign"}
|
||||
|
@ -343,14 +373,16 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
confirmButtonState={updateCollection.opts.status}
|
||||
confirmButtonState={updateCollectionOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
updateCollection.mutate({
|
||||
updateCollection({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
open={params.action === "removeImage"}
|
||||
|
@ -367,9 +399,6 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
|||
</>
|
||||
);
|
||||
}}
|
||||
</CollectionOperations>
|
||||
);
|
||||
}}
|
||||
</TypedCollectionDetailsQuery>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,12 +27,10 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
|
||||
import CollectionListPage from "../../components/CollectionListPage/CollectionListPage";
|
||||
import {
|
||||
TypedCollectionBulkDelete,
|
||||
TypedCollectionBulkPublish
|
||||
useCollectionBulkDelete,
|
||||
useCollectionBulkPublish
|
||||
} from "../../mutations";
|
||||
import { useCollectionListQuery } from "../../queries";
|
||||
import { CollectionBulkDelete } from "../../types/CollectionBulkDelete";
|
||||
import { CollectionBulkPublish } from "../../types/CollectionBulkPublish";
|
||||
import {
|
||||
collectionAddUrl,
|
||||
collectionListUrl,
|
||||
|
@ -83,6 +81,40 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
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 currentTab =
|
||||
|
@ -136,38 +168,10 @@ export const CollectionList: React.FC<CollectionListProps> = ({ 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 currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||
|
||||
return (
|
||||
<TypedCollectionBulkDelete onCompleted={handleCollectionBulkDelete}>
|
||||
{(collectionBulkDelete, collectionBulkDeleteOpts) => (
|
||||
<TypedCollectionBulkPublish onCompleted={handleCollectionBulkPublish}>
|
||||
{(collectionBulkPublish, collectionBulkPublishOpts) => (
|
||||
<>
|
||||
<CollectionListPage
|
||||
currencySymbol={currencySymbol}
|
||||
|
@ -183,9 +187,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
disabled={loading}
|
||||
collections={maybe(() =>
|
||||
data.collections.edges.map(edge => edge.node)
|
||||
)}
|
||||
collections={maybe(() => data.collections.edges.map(edge => edge.node))}
|
||||
settings={settings}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
|
@ -240,10 +242,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
toggleAll={toggleAll}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={
|
||||
params.action === "publish" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
open={params.action === "publish" && maybe(() => params.ids.length > 0)}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={collectionBulkPublishOpts.status}
|
||||
onConfirm={() =>
|
||||
|
@ -265,17 +264,14 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
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>
|
||||
)
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={
|
||||
params.action === "unpublish" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
params.action === "unpublish" && maybe(() => params.ids.length > 0)
|
||||
}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={collectionBulkPublishOpts.status}
|
||||
|
@ -298,18 +294,13 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
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>
|
||||
)
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={
|
||||
params.action === "remove" &&
|
||||
maybe(() => params.ids.length > 0)
|
||||
}
|
||||
open={params.action === "remove" && maybe(() => params.ids.length > 0)}
|
||||
onClose={closeModal}
|
||||
confirmButtonState={collectionBulkDeleteOpts.status}
|
||||
onConfirm={() =>
|
||||
|
@ -330,9 +321,7 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
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>
|
||||
)
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
|
@ -351,10 +340,6 @@ export const CollectionList: React.FC<CollectionListProps> = ({ params }) => {
|
|||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TypedCollectionBulkPublish>
|
||||
)}
|
||||
</TypedCollectionBulkDelete>
|
||||
);
|
||||
};
|
||||
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 { metadataFragment } from "./metadata";
|
||||
|
||||
export const attributeFragment = gql`
|
||||
fragment AttributeFragment on Attribute {
|
||||
id
|
||||
|
@ -13,8 +15,10 @@ export const attributeFragment = gql`
|
|||
|
||||
export const attributeDetailsFragment = gql`
|
||||
${attributeFragment}
|
||||
${metadataFragment}
|
||||
fragment AttributeDetailsFragment on Attribute {
|
||||
...AttributeFragment
|
||||
...MetadataFragment
|
||||
availableInGrid
|
||||
inputType
|
||||
storefrontSearchPosition
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { metadataFragment } from "./metadata";
|
||||
|
||||
export const categoryFragment = gql`
|
||||
fragment CategoryFragment on Category {
|
||||
id
|
||||
|
@ -13,8 +15,10 @@ export const categoryFragment = gql`
|
|||
}
|
||||
`;
|
||||
export const categoryDetailsFragment = gql`
|
||||
${metadataFragment}
|
||||
fragment CategoryDetailsFragment on Category {
|
||||
id
|
||||
...MetadataFragment
|
||||
backgroundImage {
|
||||
alt
|
||||
url
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { metadataFragment } from "./metadata";
|
||||
|
||||
export const collectionFragment = gql`
|
||||
fragment CollectionFragment on Collection {
|
||||
id
|
||||
|
@ -10,8 +12,10 @@ export const collectionFragment = gql`
|
|||
|
||||
export const collectionDetailsFragment = gql`
|
||||
${collectionFragment}
|
||||
${metadataFragment}
|
||||
fragment CollectionDetailsFragment on Collection {
|
||||
...CollectionFragment
|
||||
...MetadataFragment
|
||||
backgroundImage {
|
||||
alt
|
||||
url
|
||||
|
|
|
@ -134,3 +134,10 @@ export const pluginErrorFragment = gql`
|
|||
field
|
||||
}
|
||||
`;
|
||||
|
||||
export const metadataErrorFragment = gql`
|
||||
fragment MetadataErrorFragment on MetadataError {
|
||||
code
|
||||
field
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -5,7 +5,7 @@ export const metadataFragment = gql`
|
|||
key
|
||||
value
|
||||
}
|
||||
fragment Metadata on ObjectWithMetadata {
|
||||
fragment MetadataFragment on ObjectWithMetadata {
|
||||
metadata {
|
||||
...MetadataItem
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { attributeFragment } from "./attributes";
|
||||
import { metadataFragment } from "./metadata";
|
||||
|
||||
export const productTypeFragment = gql`
|
||||
fragment ProductTypeFragment on ProductType {
|
||||
|
@ -18,8 +19,10 @@ export const productTypeFragment = gql`
|
|||
export const productTypeDetailsFragment = gql`
|
||||
${attributeFragment}
|
||||
${productTypeFragment}
|
||||
${metadataFragment}
|
||||
fragment ProductTypeDetailsFragment on ProductType {
|
||||
...ProductTypeFragment
|
||||
...MetadataFragment
|
||||
productAttributes {
|
||||
...AttributeFragment
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { metadataFragment } from "./metadata";
|
||||
import { weightFragment } from "./weight";
|
||||
|
||||
export const stockFragment = gql`
|
||||
|
@ -105,8 +106,10 @@ export const productFragmentDetails = gql`
|
|||
${productVariantAttributesFragment}
|
||||
${stockFragment}
|
||||
${weightFragment}
|
||||
${metadataFragment}
|
||||
fragment Product on Product {
|
||||
...ProductVariantAttributesFragment
|
||||
...MetadataFragment
|
||||
name
|
||||
descriptionJson
|
||||
seoTitle
|
||||
|
@ -181,8 +184,10 @@ export const fragmentVariant = gql`
|
|||
${fragmentProductImage}
|
||||
${stockFragment}
|
||||
${weightFragment}
|
||||
${metadataFragment}
|
||||
fragment ProductVariant on ProductVariant {
|
||||
id
|
||||
...MetadataFragment
|
||||
attributes {
|
||||
attribute {
|
||||
id
|
||||
|
|
|
@ -8,6 +8,18 @@ import { AttributeInputTypeEnum, AttributeValueType } from "./../../types/global
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -24,6 +36,8 @@ export interface AttributeDetailsFragment {
|
|||
visibleInStorefront: boolean;
|
||||
filterableInDashboard: boolean;
|
||||
filterableInStorefront: boolean;
|
||||
metadata: (AttributeDetailsFragment_metadata | null)[];
|
||||
privateMetadata: (AttributeDetailsFragment_privateMetadata | null)[];
|
||||
availableInGrid: boolean;
|
||||
inputType: AttributeInputTypeEnum | null;
|
||||
storefrontSearchPosition: number;
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -20,6 +32,8 @@ export interface CategoryDetailsFragment_parent {
|
|||
export interface CategoryDetailsFragment {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
metadata: (CategoryDetailsFragment_metadata | null)[];
|
||||
privateMetadata: (CategoryDetailsFragment_privateMetadata | null)[];
|
||||
backgroundImage: CategoryDetailsFragment_backgroundImage | null;
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
|
|
|
@ -6,6 +6,18 @@
|
|||
// 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 {
|
||||
__typename: "Image";
|
||||
alt: string | null;
|
||||
|
@ -17,6 +29,8 @@ export interface CollectionDetailsFragment {
|
|||
id: string;
|
||||
isPublished: boolean;
|
||||
name: string;
|
||||
metadata: (CollectionDetailsFragment_metadata | null)[];
|
||||
privateMetadata: (CollectionDetailsFragment_privateMetadata | null)[];
|
||||
backgroundImage: CollectionDetailsFragment_backgroundImage | null;
|
||||
descriptionJson: any;
|
||||
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;
|
||||
}
|
||||
|
||||
export interface Product_metadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Product_privateMetadata {
|
||||
__typename: "MetadataItem";
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Product_category {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -180,6 +192,8 @@ export interface Product {
|
|||
attributes: Product_attributes[];
|
||||
productType: Product_productType;
|
||||
pricing: Product_pricing | null;
|
||||
metadata: (Product_metadata | null)[];
|
||||
privateMetadata: (Product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface ProductTypeDetailsFragment_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -47,6 +59,8 @@ export interface ProductTypeDetailsFragment {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
taxType: ProductTypeDetailsFragment_taxType | null;
|
||||
metadata: (ProductTypeDetailsFragment_metadata | null)[];
|
||||
privateMetadata: (ProductTypeDetailsFragment_privateMetadata | null)[];
|
||||
productAttributes: (ProductTypeDetailsFragment_productAttributes | null)[] | null;
|
||||
variantAttributes: (ProductTypeDetailsFragment_variantAttributes | null)[] | null;
|
||||
weight: ProductTypeDetailsFragment_weight | null;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { WeightUnitsEnum } from "./../../types/globalTypes";
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -114,6 +126,8 @@ export interface ProductVariant_weight {
|
|||
export interface ProductVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (ProductVariant_metadata | null)[];
|
||||
privateMetadata: (ProductVariant_privateMetadata | null)[];
|
||||
attributes: ProductVariant_attributes[];
|
||||
costPrice: ProductVariant_costPrice | null;
|
||||
images: (ProductVariant_images | null)[] | null;
|
||||
|
|
|
@ -5,6 +5,8 @@ import Container from "@saleor/components/Container";
|
|||
import ControlledSwitch from "@saleor/components/ControlledSwitch";
|
||||
import Form from "@saleor/components/Form";
|
||||
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 SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
|
||||
|
@ -13,6 +15,8 @@ import { sectionNames } from "@saleor/intl";
|
|||
import { maybe } from "@saleor/misc";
|
||||
import { ListActions, ReorderEvent, UserError } from "@saleor/types";
|
||||
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 { useIntl } from "react-intl";
|
||||
|
||||
|
@ -30,7 +34,7 @@ interface ChoiceType {
|
|||
value: string;
|
||||
}
|
||||
|
||||
export interface ProductTypeForm {
|
||||
export interface ProductTypeForm extends MetadataFormData {
|
||||
name: string;
|
||||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
|
@ -92,6 +96,12 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
|||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const [taxTypeDisplayName, setTaxTypeDisplayName] = useStateFromProps(
|
||||
maybe(() => productType.taxType.description, "")
|
||||
);
|
||||
|
@ -104,7 +114,9 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
|||
maybe(() => productType.isShippingRequired) !== undefined
|
||||
? productType.isShippingRequired
|
||||
: false,
|
||||
metadata: productType?.metadata?.map(mapMetadataItemToInput),
|
||||
name: maybe(() => productType.name) !== undefined ? productType.name : "",
|
||||
privateMetadata: productType?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
productAttributes:
|
||||
maybe(() => productType.productAttributes) !== undefined
|
||||
? productType.productAttributes.map(attribute => ({
|
||||
|
@ -122,9 +134,26 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
|||
: [],
|
||||
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 (
|
||||
<Form initial={formInitialData} onSubmit={handleSubmit} confirmLeave>
|
||||
{({ change, data, hasChanged, submit }) => {
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<Form initial={formInitialData} onSubmit={onSubmit} confirmLeave>
|
||||
{({ change, data, hasChanged, submit }) => (
|
||||
<Container>
|
||||
<AppHeader onBack={onBack}>
|
||||
{intl.formatMessage(sectionNames.productTypes)}
|
||||
|
@ -194,6 +223,8 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</div>
|
||||
<div>
|
||||
<ProductTypeShipping
|
||||
|
@ -212,7 +243,8 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
|
|||
state={saveButtonBarState}
|
||||
/>
|
||||
</Container>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
ProductTypeAttributeReorderMutation,
|
||||
TypedAssignAttributeMutation,
|
||||
TypedProductTypeDeleteMutation,
|
||||
TypedProductTypeUpdateMutation,
|
||||
TypedUnassignAttributeMutation
|
||||
} from "../mutations";
|
||||
import {
|
||||
|
@ -27,10 +26,6 @@ import {
|
|||
ProductTypeDelete,
|
||||
ProductTypeDeleteVariables
|
||||
} from "../types/ProductTypeDelete";
|
||||
import {
|
||||
ProductTypeUpdate,
|
||||
ProductTypeUpdateVariables
|
||||
} from "../types/ProductTypeUpdate";
|
||||
import {
|
||||
UnassignAttribute,
|
||||
UnassignAttributeVariables
|
||||
|
@ -75,17 +70,12 @@ interface ProductTypeOperationsProps {
|
|||
ProductTypeAttributeReorder,
|
||||
ProductTypeAttributeReorderVariables
|
||||
>;
|
||||
updateProductType: PartialMutationProviderOutput<
|
||||
ProductTypeUpdate,
|
||||
ProductTypeUpdateVariables
|
||||
>;
|
||||
}) => React.ReactNode;
|
||||
productType: ProductTypeDetailsFragment;
|
||||
onAssignAttribute: (data: AssignAttribute) => void;
|
||||
onUnassignAttribute: (data: UnassignAttribute) => void;
|
||||
onProductTypeAttributeReorder: (data: ProductTypeAttributeReorder) => void;
|
||||
onProductTypeDelete: (data: ProductTypeDelete) => void;
|
||||
onProductTypeUpdate: (data: ProductTypeUpdate) => void;
|
||||
}
|
||||
|
||||
const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
||||
|
@ -94,13 +84,10 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
|||
onAssignAttribute,
|
||||
onUnassignAttribute,
|
||||
onProductTypeAttributeReorder,
|
||||
onProductTypeDelete,
|
||||
onProductTypeUpdate
|
||||
onProductTypeDelete
|
||||
}) => (
|
||||
<TypedProductTypeDeleteMutation onCompleted={onProductTypeDelete}>
|
||||
{(...deleteProductType) => (
|
||||
<TypedProductTypeUpdateMutation onCompleted={onProductTypeUpdate}>
|
||||
{(...updateProductType) => (
|
||||
<TypedAssignAttributeMutation onCompleted={onAssignAttribute}>
|
||||
{(...assignAttribute) => (
|
||||
<TypedUnassignAttributeMutation onCompleted={onUnassignAttribute}>
|
||||
|
@ -108,10 +95,7 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
|||
<ProductTypeAttributeReorderMutation
|
||||
onCompleted={onProductTypeAttributeReorder}
|
||||
>
|
||||
{(
|
||||
reorderAttributeMutation,
|
||||
reorderAttributeMutationResult
|
||||
) => {
|
||||
{(reorderAttributeMutation, reorderAttributeMutationResult) => {
|
||||
const reorderAttributeMutationFn: MutationFunction<
|
||||
ProductTypeAttributeReorder,
|
||||
ProductTypeAttributeReorderVariables
|
||||
|
@ -123,16 +107,14 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
|||
productType: {
|
||||
...productType,
|
||||
productAttributes:
|
||||
opts.variables.type ===
|
||||
AttributeTypeEnum.PRODUCT
|
||||
opts.variables.type === AttributeTypeEnum.PRODUCT
|
||||
? moveAttribute(
|
||||
productType.productAttributes,
|
||||
opts.variables.move
|
||||
)
|
||||
: productType.productAttributes,
|
||||
variantAttributes:
|
||||
opts.variables.type ===
|
||||
AttributeTypeEnum.VARIANT
|
||||
opts.variables.type === AttributeTypeEnum.VARIANT
|
||||
? moveAttribute(
|
||||
productType.variantAttributes,
|
||||
opts.variables.move
|
||||
|
@ -160,9 +142,6 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
|||
),
|
||||
unassignAttribute: getMutationProviderData(
|
||||
...unassignAttribute
|
||||
),
|
||||
updateProductType: getMutationProviderData(
|
||||
...updateProductType
|
||||
)
|
||||
});
|
||||
}}
|
||||
|
@ -172,8 +151,6 @@ const ProductTypeOperations: React.FC<ProductTypeOperationsProps> = ({
|
|||
)}
|
||||
</TypedAssignAttributeMutation>
|
||||
)}
|
||||
</TypedProductTypeUpdateMutation>
|
||||
)}
|
||||
</TypedProductTypeDeleteMutation>
|
||||
);
|
||||
export default ProductTypeOperations;
|
||||
|
|
|
@ -550,7 +550,15 @@ export const productType: ProductTypeDetails_productType = {
|
|||
hasVariants: false,
|
||||
id: "UHJvZHVjdFR5cGU6NQ==",
|
||||
isShippingRequired: false,
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "E-books",
|
||||
privateMetadata: [],
|
||||
productAttributes: [
|
||||
{
|
||||
__typename: "Attribute" as "Attribute",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { productTypeDetailsFragment } from "@saleor/fragments/productTypes";
|
||||
import makeMutation from "@saleor/hooks/makeMutation";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedMutation } from "../mutations";
|
||||
|
@ -78,7 +79,7 @@ export const productTypeUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductTypeUpdateMutation = TypedMutation<
|
||||
export const useProductTypeUpdateMutation = makeMutation<
|
||||
ProductTypeUpdate,
|
||||
ProductTypeUpdateVariables
|
||||
>(productTypeUpdateMutation);
|
||||
|
|
|
@ -20,6 +20,18 @@ export interface AssignAttribute_attributeAssign_productType_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -53,6 +65,8 @@ export interface AssignAttribute_attributeAssign_productType {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
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;
|
||||
variantAttributes: (AssignAttribute_attributeAssign_productType_variantAttributes | null)[] | null;
|
||||
weight: AssignAttribute_attributeAssign_productType_weight | null;
|
||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -53,6 +65,8 @@ export interface ProductTypeAttributeReorder_productTypeReorderAttributes_produc
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
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;
|
||||
variantAttributes: (ProductTypeAttributeReorder_productTypeReorderAttributes_productType_variantAttributes | null)[] | null;
|
||||
weight: ProductTypeAttributeReorder_productTypeReorderAttributes_productType_weight | null;
|
||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeCreate_productTypeCreate_productType_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -53,6 +65,8 @@ export interface ProductTypeCreate_productTypeCreate_productType {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
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;
|
||||
variantAttributes: (ProductTypeCreate_productTypeCreate_productType_variantAttributes | null)[] | null;
|
||||
weight: ProductTypeCreate_productTypeCreate_productType_weight | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface ProductTypeDetails_productType_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -47,6 +59,8 @@ export interface ProductTypeDetails_productType {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
taxType: ProductTypeDetails_productType_taxType | null;
|
||||
metadata: (ProductTypeDetails_productType_metadata | null)[];
|
||||
privateMetadata: (ProductTypeDetails_productType_privateMetadata | null)[];
|
||||
productAttributes: (ProductTypeDetails_productType_productAttributes | null)[] | null;
|
||||
variantAttributes: (ProductTypeDetails_productType_variantAttributes | null)[] | null;
|
||||
weight: ProductTypeDetails_productType_weight | null;
|
||||
|
|
|
@ -20,6 +20,18 @@ export interface ProductTypeUpdate_productTypeUpdate_productType_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -53,6 +65,8 @@ export interface ProductTypeUpdate_productTypeUpdate_productType {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
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;
|
||||
variantAttributes: (ProductTypeUpdate_productTypeUpdate_productType_variantAttributes | null)[] | null;
|
||||
weight: ProductTypeUpdate_productTypeUpdate_productType_weight | null;
|
||||
|
|
|
@ -20,6 +20,18 @@ export interface UnassignAttribute_attributeUnassign_productType_taxType {
|
|||
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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
|
@ -53,6 +65,8 @@ export interface UnassignAttribute_attributeUnassign_productType {
|
|||
hasVariants: boolean;
|
||||
isShippingRequired: boolean;
|
||||
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;
|
||||
variantAttributes: (UnassignAttribute_attributeUnassign_productType_variantAttributes | null)[] | 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 { maybe } from "@saleor/misc";
|
||||
import AssignAttributeDialog from "@saleor/productTypes/components/AssignAttributeDialog";
|
||||
import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
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 { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -25,14 +31,12 @@ import useAvailableAttributeSearch from "../../hooks/useAvailableAttributeSearch
|
|||
import { TypedProductTypeDetailsQuery } from "../../queries";
|
||||
import { AssignAttribute } from "../../types/AssignAttribute";
|
||||
import { ProductTypeDelete } from "../../types/ProductTypeDelete";
|
||||
import { ProductTypeUpdate as ProductTypeUpdateMutation } from "../../types/ProductTypeUpdate";
|
||||
import { UnassignAttribute } from "../../types/UnassignAttribute";
|
||||
import {
|
||||
productTypeListUrl,
|
||||
productTypeUrl,
|
||||
ProductTypeUrlQueryParams
|
||||
} from "../../urls";
|
||||
import { ProductTypeUpdateErrors } from "./errors";
|
||||
|
||||
interface ProductTypeUpdateProps {
|
||||
id: string;
|
||||
|
@ -54,12 +58,66 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
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 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 (
|
||||
<ProductTypeUpdateErrors>
|
||||
{({ errors, set: setErrors }) => (
|
||||
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
||||
{({ data, loading: dataLoading }) => {
|
||||
const productType = data?.productType;
|
||||
|
@ -81,12 +139,13 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
data.attributeAssign.errors !== null &&
|
||||
data.attributeAssign.errors.length > 0
|
||||
) {
|
||||
setErrors.addAttributeErrors(data.attributeAssign.errors);
|
||||
setErrors(prevErrors => ({
|
||||
...prevErrors,
|
||||
addAttributeErrors: data.attributeAssign.errors
|
||||
}));
|
||||
}
|
||||
};
|
||||
const handleAttributeUnassignSuccess = (
|
||||
data: UnassignAttribute
|
||||
) => {
|
||||
const handleAttributeUnassignSuccess = (data: UnassignAttribute) => {
|
||||
if (data.attributeUnassign.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
|
@ -110,24 +169,12 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
navigate(productTypeListUrl(), true);
|
||||
}
|
||||
};
|
||||
const handleProductTypeUpdateSuccess = (
|
||||
updateData: ProductTypeUpdateMutation
|
||||
) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
const handleSubmit = createMetadataUpdateHandler(
|
||||
data?.productType,
|
||||
handleProductTypeUpdate,
|
||||
variables => updateMetadata({ variables }),
|
||||
variables => updatePrivateMetadata({ variables })
|
||||
);
|
||||
|
||||
return (
|
||||
<ProductTypeOperations
|
||||
|
@ -135,46 +182,24 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
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({
|
||||
const handleProductTypeVariantsToggle = (hasVariants: boolean) =>
|
||||
updateProductType({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
hasVariants
|
||||
}
|
||||
}
|
||||
});
|
||||
const handleAssignAttribute = () =>
|
||||
assignAttribute.mutate({
|
||||
|
@ -197,7 +222,7 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
ids: params.ids
|
||||
});
|
||||
|
||||
const loading = updateProductType.opts.loading || dataLoading;
|
||||
const loading = updateProductTypeOpts.loading || dataLoading;
|
||||
|
||||
const handleAttributeReorder = (
|
||||
event: ReorderEvent,
|
||||
|
@ -222,14 +247,12 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
<>
|
||||
<WindowTitle title={maybe(() => data.productType.name)} />
|
||||
<ProductTypeDetailsPage
|
||||
defaultWeightUnit={maybe(
|
||||
() => data.shop.defaultWeightUnit
|
||||
)}
|
||||
defaultWeightUnit={maybe(() => data.shop.defaultWeightUnit)}
|
||||
disabled={loading}
|
||||
errors={errors.formErrors}
|
||||
pageTitle={maybe(() => data.productType.name)}
|
||||
productType={maybe(() => data.productType)}
|
||||
saveButtonBarState={updateProductType.opts.status}
|
||||
saveButtonBarState={updateProductTypeOpts.status}
|
||||
taxTypes={maybe(() => data.taxTypes, [])}
|
||||
onAttributeAdd={type =>
|
||||
navigate(
|
||||
|
@ -260,11 +283,10 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
)
|
||||
}
|
||||
onHasVariantsToggle={handleProductTypeVariantsToggle}
|
||||
onSubmit={handleProductTypeUpdate}
|
||||
onSubmit={handleSubmit}
|
||||
productAttributeList={{
|
||||
isChecked: productAttributeListActions.isSelected,
|
||||
selected:
|
||||
productAttributeListActions.listElements.length,
|
||||
selected: productAttributeListActions.listElements.length,
|
||||
toggle: productAttributeListActions.toggle,
|
||||
toggleAll: productAttributeListActions.toggleAll,
|
||||
toolbar: (
|
||||
|
@ -274,8 +296,7 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
navigate(
|
||||
productTypeUrl(id, {
|
||||
action: "unassign-attributes",
|
||||
ids:
|
||||
productAttributeListActions.listElements
|
||||
ids: productAttributeListActions.listElements
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -289,8 +310,7 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
}}
|
||||
variantAttributeList={{
|
||||
isChecked: variantAttributeListActions.isSelected,
|
||||
selected:
|
||||
variantAttributeListActions.listElements.length,
|
||||
selected: variantAttributeListActions.listElements.length,
|
||||
toggle: variantAttributeListActions.toggle,
|
||||
toggleAll: variantAttributeListActions.toggleAll,
|
||||
toolbar: (
|
||||
|
@ -300,8 +320,7 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
navigate(
|
||||
productTypeUrl(id, {
|
||||
action: "unassign-attributes",
|
||||
ids:
|
||||
variantAttributeListActions.listElements
|
||||
ids: variantAttributeListActions.listElements
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -338,8 +357,8 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
onOpen={result.refetch}
|
||||
hasMore={maybe(
|
||||
() =>
|
||||
result.data.productType.availableAttributes
|
||||
.pageInfo.hasNextPage,
|
||||
result.data.productType.availableAttributes.pageInfo
|
||||
.hasNextPage,
|
||||
false
|
||||
)}
|
||||
open={
|
||||
|
@ -376,10 +395,7 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
onClose={closeModal}
|
||||
onConfirm={handleBulkAttributeUnassign}
|
||||
open={params.action === "unassign-attributes"}
|
||||
productTypeName={maybe(
|
||||
() => data.productType.name,
|
||||
"..."
|
||||
)}
|
||||
productTypeName={maybe(() => data.productType.name, "...")}
|
||||
/>
|
||||
<ProductTypeAttributeUnassignDialog
|
||||
attributeName={maybe(
|
||||
|
@ -387,18 +403,14 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
[
|
||||
...data.productType.productAttributes,
|
||||
...data.productType.variantAttributes
|
||||
].find(attribute => attribute.id === params.id)
|
||||
.name,
|
||||
].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,
|
||||
"..."
|
||||
)}
|
||||
productTypeName={maybe(() => data.productType.name, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -407,8 +419,6 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
);
|
||||
}}
|
||||
</TypedProductTypeDetailsQuery>
|
||||
)}
|
||||
</ProductTypeUpdateErrors>
|
||||
);
|
||||
};
|
||||
export default ProductTypeUpdate;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
|||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
import Grid from "@saleor/components/Grid";
|
||||
import Metadata from "@saleor/components/Metadata/Metadata";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
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 createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
|
||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import { convertFromRaw, RawDraftContentState } from "draft-js";
|
||||
import { diff } from "fast-array-diff";
|
||||
import React from "react";
|
||||
|
@ -155,6 +157,12 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
getChoices(maybe(() => product.collections, []))
|
||||
);
|
||||
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const initialData = getProductUpdatePageFormData(product, variants);
|
||||
const initialDescription = maybe<RawDraftContentState>(() =>
|
||||
JSON.parse(product.descriptionJson)
|
||||
|
@ -167,11 +175,18 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
const hasVariants = maybe(() => product.productType.hasVariants, false);
|
||||
|
||||
const handleSubmit = (data: ProductUpdatePageFormData) => {
|
||||
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||
const privateMetadata = isPrivateMetadataModified
|
||||
? data.privateMetadata
|
||||
: undefined;
|
||||
|
||||
if (product.productType.hasVariants) {
|
||||
onSubmit({
|
||||
...data,
|
||||
addStocks: [],
|
||||
attributes,
|
||||
metadata,
|
||||
privateMetadata,
|
||||
removeStocks: [],
|
||||
updateStocks: []
|
||||
});
|
||||
|
@ -188,6 +203,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
),
|
||||
attributes,
|
||||
metadata,
|
||||
privateMetadata,
|
||||
removeStocks: stockDiff.removed,
|
||||
updateStocks: stocks.filter(
|
||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
|
@ -224,6 +241,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
attributes,
|
||||
triggerChange
|
||||
);
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -347,6 +365,8 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
|||
"Add search engine title and description to make this product easier to find"
|
||||
})}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</div>
|
||||
<div>
|
||||
<ProductOrganization
|
||||
|
|
|
@ -4,6 +4,8 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
|||
import Container from "@saleor/components/Container";
|
||||
import Form from "@saleor/components/Form";
|
||||
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 SaveButtonBar from "@saleor/components/SaveButtonBar";
|
||||
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
||||
|
@ -17,6 +19,8 @@ import {
|
|||
getAttributeInputFromVariant,
|
||||
getStockInputFromVariant
|
||||
} 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 React from "react";
|
||||
|
||||
|
@ -31,7 +35,7 @@ import ProductVariantImageSelectDialog from "../ProductVariantImageSelectDialog"
|
|||
import ProductVariantNavigation from "../ProductVariantNavigation";
|
||||
import ProductVariantPrice from "../ProductVariantPrice";
|
||||
|
||||
export interface ProductVariantPageFormData {
|
||||
export interface ProductVariantPageFormData extends MetadataFormData {
|
||||
costPrice: string;
|
||||
price: string;
|
||||
sku: string;
|
||||
|
@ -100,6 +104,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
const [isModalOpened, setModalStatus] = React.useState(false);
|
||||
const toggleModal = () => setModalStatus(!isModalOpened);
|
||||
|
||||
const {
|
||||
isMetadataModified,
|
||||
isPrivateMetadataModified,
|
||||
makeChangeHandler: makeMetadataChangeHandler
|
||||
} = useMetadataChangeTrigger();
|
||||
|
||||
const variantImages = maybe(() => variant.images.map(image => image.id), []);
|
||||
const productImages = maybe(() =>
|
||||
variant.product.images.sort((prev, next) =>
|
||||
|
@ -114,7 +124,9 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
|
||||
const initialForm: ProductVariantPageFormData = {
|
||||
costPrice: maybe(() => variant.costPrice.amount.toString(), ""),
|
||||
metadata: variant?.metadata?.map(mapMetadataItemToInput),
|
||||
price: maybe(() => variant.price.amount.toString(), ""),
|
||||
privateMetadata: variant?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
sku: maybe(() => variant.sku, ""),
|
||||
trackInventory: variant?.trackInventory,
|
||||
weight: variant?.weight?.value.toString() || ""
|
||||
|
@ -124,6 +136,10 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
const dataStocks = stocks.map(stock => stock.id);
|
||||
const variantStocks = variant.stocks.map(stock => stock.warehouse.id);
|
||||
const stockDiff = diff(variantStocks, dataStocks);
|
||||
const metadata = isMetadataModified ? data.metadata : undefined;
|
||||
const privateMetadata = isPrivateMetadataModified
|
||||
? data.privateMetadata
|
||||
: undefined;
|
||||
|
||||
onSubmit({
|
||||
...data,
|
||||
|
@ -131,6 +147,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
),
|
||||
attributes,
|
||||
metadata,
|
||||
privateMetadata,
|
||||
removeStocks: stockDiff.removed,
|
||||
updateStocks: stocks.filter(
|
||||
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
|
||||
|
@ -152,6 +170,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
triggerChange();
|
||||
};
|
||||
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid variant="inverted">
|
||||
|
@ -235,6 +255,8 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
|||
removeStock(id);
|
||||
}}
|
||||
/>
|
||||
<CardSpacer />
|
||||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</div>
|
||||
</Grid>
|
||||
<SaveButtonBar
|
||||
|
|
|
@ -153,6 +153,13 @@ export const product: (
|
|||
isFeatured: false,
|
||||
isPublished: true,
|
||||
margin: { __typename: "Margin", start: 2, stop: 7 },
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "Ergonomic Plastic Bacon",
|
||||
pricing: {
|
||||
__typename: "ProductPricingInfo",
|
||||
|
@ -186,6 +193,7 @@ export const product: (
|
|||
}
|
||||
}
|
||||
},
|
||||
privateMetadata: [],
|
||||
productType: {
|
||||
__typename: "ProductType",
|
||||
hasVariants: true,
|
||||
|
@ -1516,12 +1524,20 @@ export const variant = (placeholderImage: string): ProductVariant => ({
|
|||
url: placeholderImage
|
||||
}
|
||||
],
|
||||
metadata: [
|
||||
{
|
||||
__typename: "MetadataItem",
|
||||
key: "integration.id",
|
||||
value: "100023123"
|
||||
}
|
||||
],
|
||||
name: "Extended Hard",
|
||||
price: {
|
||||
__typename: "Money",
|
||||
amount: 100,
|
||||
currency: "USD"
|
||||
},
|
||||
privateMetadata: [],
|
||||
product: {
|
||||
__typename: "Product" as "Product",
|
||||
id: "prod1",
|
||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductCreate_productCreate_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -186,6 +198,8 @@ export interface ProductCreate_productCreate_product {
|
|||
attributes: ProductCreate_productCreate_product_attributes[];
|
||||
productType: ProductCreate_productCreate_product_productType;
|
||||
pricing: ProductCreate_productCreate_product_pricing | null;
|
||||
metadata: (ProductCreate_productCreate_product_metadata | null)[];
|
||||
privateMetadata: (ProductCreate_productCreate_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -93,6 +93,18 @@ export interface ProductDetails_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -180,6 +192,8 @@ export interface ProductDetails_product {
|
|||
attributes: ProductDetails_product_attributes[];
|
||||
productType: ProductDetails_product_productType;
|
||||
pricing: ProductDetails_product_pricing | null;
|
||||
metadata: (ProductDetails_product_metadata | null)[];
|
||||
privateMetadata: (ProductDetails_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductImageCreate_productImageCreate_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -186,6 +198,8 @@ export interface ProductImageCreate_productImageCreate_product {
|
|||
attributes: ProductImageCreate_productImageCreate_product_attributes[];
|
||||
productType: ProductImageCreate_productImageCreate_product_productType;
|
||||
pricing: ProductImageCreate_productImageCreate_product_pricing | null;
|
||||
metadata: (ProductImageCreate_productImageCreate_product_metadata | null)[];
|
||||
privateMetadata: (ProductImageCreate_productImageCreate_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductImageUpdate_productImageUpdate_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -186,6 +198,8 @@ export interface ProductImageUpdate_productImageUpdate_product {
|
|||
attributes: ProductImageUpdate_productImageUpdate_product_attributes[];
|
||||
productType: ProductImageUpdate_productImageUpdate_product_productType;
|
||||
pricing: ProductImageUpdate_productImageUpdate_product_pricing | null;
|
||||
metadata: (ProductImageUpdate_productImageUpdate_product_metadata | null)[];
|
||||
privateMetadata: (ProductImageUpdate_productImageUpdate_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -99,6 +99,18 @@ export interface ProductUpdate_productUpdate_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -186,6 +198,8 @@ export interface ProductUpdate_productUpdate_product {
|
|||
attributes: ProductUpdate_productUpdate_product_attributes[];
|
||||
productType: ProductUpdate_productUpdate_product_productType;
|
||||
pricing: ProductUpdate_productUpdate_product_pricing | null;
|
||||
metadata: (ProductUpdate_productUpdate_product_metadata | null)[];
|
||||
privateMetadata: (ProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
|
|
@ -8,6 +8,18 @@ import { WeightUnitsEnum } from "./../../types/globalTypes";
|
|||
// 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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -114,6 +126,8 @@ export interface ProductVariantDetails_productVariant_weight {
|
|||
export interface ProductVariantDetails_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (ProductVariantDetails_productVariant_metadata | null)[];
|
||||
privateMetadata: (ProductVariantDetails_productVariant_privateMetadata | null)[];
|
||||
attributes: ProductVariantDetails_productVariant_attributes[];
|
||||
costPrice: ProductVariantDetails_productVariant_costPrice | null;
|
||||
images: (ProductVariantDetails_productVariant_images | null)[] | null;
|
||||
|
|
|
@ -99,6 +99,18 @@ export interface SimpleProductUpdate_productUpdate_product_pricing {
|
|||
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 {
|
||||
__typename: "Category";
|
||||
id: string;
|
||||
|
@ -186,6 +198,8 @@ export interface SimpleProductUpdate_productUpdate_product {
|
|||
attributes: SimpleProductUpdate_productUpdate_product_attributes[];
|
||||
productType: SimpleProductUpdate_productUpdate_product_productType;
|
||||
pricing: SimpleProductUpdate_productUpdate_product_pricing | null;
|
||||
metadata: (SimpleProductUpdate_productUpdate_product_metadata | null)[];
|
||||
privateMetadata: (SimpleProductUpdate_productUpdate_product_privateMetadata | null)[];
|
||||
name: string;
|
||||
descriptionJson: any;
|
||||
seoTitle: string | null;
|
||||
|
@ -215,6 +229,18 @@ export interface SimpleProductUpdate_productVariantUpdate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -321,6 +347,8 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant_weight
|
|||
export interface SimpleProductUpdate_productVariantUpdate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (SimpleProductUpdate_productVariantUpdate_productVariant_metadata | null)[];
|
||||
privateMetadata: (SimpleProductUpdate_productVariantUpdate_productVariant_privateMetadata | null)[];
|
||||
attributes: SimpleProductUpdate_productVariantUpdate_productVariant_attributes[];
|
||||
costPrice: SimpleProductUpdate_productVariantUpdate_productVariant_costPrice | null;
|
||||
images: (SimpleProductUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
||||
|
@ -346,6 +374,18 @@ export interface SimpleProductUpdate_productVariantStocksCreate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -452,6 +492,8 @@ export interface SimpleProductUpdate_productVariantStocksCreate_productVariant_w
|
|||
export interface SimpleProductUpdate_productVariantStocksCreate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (SimpleProductUpdate_productVariantStocksCreate_productVariant_metadata | null)[];
|
||||
privateMetadata: (SimpleProductUpdate_productVariantStocksCreate_productVariant_privateMetadata | null)[];
|
||||
attributes: SimpleProductUpdate_productVariantStocksCreate_productVariant_attributes[];
|
||||
costPrice: SimpleProductUpdate_productVariantStocksCreate_productVariant_costPrice | null;
|
||||
images: (SimpleProductUpdate_productVariantStocksCreate_productVariant_images | null)[] | null;
|
||||
|
@ -476,6 +518,18 @@ export interface SimpleProductUpdate_productVariantStocksDelete_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -582,6 +636,8 @@ export interface SimpleProductUpdate_productVariantStocksDelete_productVariant_w
|
|||
export interface SimpleProductUpdate_productVariantStocksDelete_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (SimpleProductUpdate_productVariantStocksDelete_productVariant_metadata | null)[];
|
||||
privateMetadata: (SimpleProductUpdate_productVariantStocksDelete_productVariant_privateMetadata | null)[];
|
||||
attributes: SimpleProductUpdate_productVariantStocksDelete_productVariant_attributes[];
|
||||
costPrice: SimpleProductUpdate_productVariantStocksDelete_productVariant_costPrice | null;
|
||||
images: (SimpleProductUpdate_productVariantStocksDelete_productVariant_images | null)[] | null;
|
||||
|
@ -607,6 +663,18 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -713,6 +781,8 @@ export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant_w
|
|||
export interface SimpleProductUpdate_productVariantStocksUpdate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_metadata | null)[];
|
||||
privateMetadata: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_privateMetadata | null)[];
|
||||
attributes: SimpleProductUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
||||
costPrice: SimpleProductUpdate_productVariantStocksUpdate_productVariant_costPrice | null;
|
||||
images: (SimpleProductUpdate_productVariantStocksUpdate_productVariant_images | null)[] | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantCreate_productVariantCreate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -120,6 +132,8 @@ export interface VariantCreate_productVariantCreate_productVariant_weight {
|
|||
export interface VariantCreate_productVariantCreate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (VariantCreate_productVariantCreate_productVariant_metadata | null)[];
|
||||
privateMetadata: (VariantCreate_productVariantCreate_productVariant_privateMetadata | null)[];
|
||||
attributes: VariantCreate_productVariantCreate_productVariant_attributes[];
|
||||
costPrice: VariantCreate_productVariantCreate_productVariant_costPrice | null;
|
||||
images: (VariantCreate_productVariantCreate_productVariant_images | null)[] | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantImageAssign_variantImageAssign_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -120,6 +132,8 @@ export interface VariantImageAssign_variantImageAssign_productVariant_weight {
|
|||
export interface VariantImageAssign_variantImageAssign_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (VariantImageAssign_variantImageAssign_productVariant_metadata | null)[];
|
||||
privateMetadata: (VariantImageAssign_variantImageAssign_productVariant_privateMetadata | null)[];
|
||||
attributes: VariantImageAssign_variantImageAssign_productVariant_attributes[];
|
||||
costPrice: VariantImageAssign_variantImageAssign_productVariant_costPrice | null;
|
||||
images: (VariantImageAssign_variantImageAssign_productVariant_images | null)[] | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantImageUnassign_variantImageUnassign_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -120,6 +132,8 @@ export interface VariantImageUnassign_variantImageUnassign_productVariant_weight
|
|||
export interface VariantImageUnassign_variantImageUnassign_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (VariantImageUnassign_variantImageUnassign_productVariant_metadata | null)[];
|
||||
privateMetadata: (VariantImageUnassign_variantImageUnassign_productVariant_privateMetadata | null)[];
|
||||
attributes: VariantImageUnassign_variantImageUnassign_productVariant_attributes[];
|
||||
costPrice: VariantImageUnassign_variantImageUnassign_productVariant_costPrice | null;
|
||||
images: (VariantImageUnassign_variantImageUnassign_productVariant_images | null)[] | null;
|
||||
|
|
|
@ -14,6 +14,18 @@ export interface VariantUpdate_productVariantUpdate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -120,6 +132,8 @@ export interface VariantUpdate_productVariantUpdate_productVariant_weight {
|
|||
export interface VariantUpdate_productVariantUpdate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (VariantUpdate_productVariantUpdate_productVariant_metadata | null)[];
|
||||
privateMetadata: (VariantUpdate_productVariantUpdate_productVariant_privateMetadata | null)[];
|
||||
attributes: VariantUpdate_productVariantUpdate_productVariant_attributes[];
|
||||
costPrice: VariantUpdate_productVariantUpdate_productVariant_costPrice | null;
|
||||
images: (VariantUpdate_productVariantUpdate_productVariant_images | null)[] | null;
|
||||
|
@ -145,6 +159,18 @@ export interface VariantUpdate_productVariantStocksUpdate_errors {
|
|||
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 {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
|
@ -251,6 +277,8 @@ export interface VariantUpdate_productVariantStocksUpdate_productVariant_weight
|
|||
export interface VariantUpdate_productVariantStocksUpdate_productVariant {
|
||||
__typename: "ProductVariant";
|
||||
id: string;
|
||||
metadata: (VariantUpdate_productVariantStocksUpdate_productVariant_metadata | null)[];
|
||||
privateMetadata: (VariantUpdate_productVariantStocksUpdate_productVariant_privateMetadata | null)[];
|
||||
attributes: VariantUpdate_productVariantStocksUpdate_productVariant_attributes[];
|
||||
costPrice: VariantUpdate_productVariantStocksUpdate_productVariant_costPrice | 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 { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { ProductVariant } from "@saleor/fragments/types/ProductVariant";
|
||||
|
@ -10,6 +11,7 @@ import {
|
|||
} from "@saleor/products/types/ProductDetails";
|
||||
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
|
||||
import { StockInput } from "@saleor/types/globalTypes";
|
||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import { RawDraftContentState } from "draft-js";
|
||||
|
||||
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;
|
||||
category: string | null;
|
||||
collections: string[];
|
||||
|
@ -198,7 +200,9 @@ export function getProductUpdatePageFormData(
|
|||
),
|
||||
description: maybe(() => JSON.parse(product.descriptionJson)),
|
||||
isPublished: maybe(() => product.isPublished, false),
|
||||
metadata: product?.metadata?.map(mapMetadataItemToInput),
|
||||
name: maybe(() => product.name, ""),
|
||||
privateMetadata: product?.privateMetadata?.map(mapMetadataItemToInput),
|
||||
publicationDate: maybe(() => product.publicationDate, ""),
|
||||
seoDescription: maybe(() => product.seoDescription, ""),
|
||||
seoTitle: maybe(() => product.seoTitle, ""),
|
||||
|
|
|
@ -23,6 +23,11 @@ import {
|
|||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
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 React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
@ -81,6 +86,8 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
}
|
||||
});
|
||||
const shop = useShop();
|
||||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const { data, loading, refetch } = useProductDetails({
|
||||
displayLoader: true,
|
||||
|
@ -181,10 +188,15 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
deleteProductImage({ variables: { id } });
|
||||
const handleImageEdit = (imageId: string) => () =>
|
||||
navigate(productImageUrl(id, imageId));
|
||||
const handleSubmit = createUpdateHandler(
|
||||
const handleSubmit = createMetadataUpdateHandler(
|
||||
product,
|
||||
createUpdateHandler(
|
||||
product,
|
||||
variables => updateProduct({ variables }),
|
||||
variables => updateSimpleProduct({ variables })
|
||||
),
|
||||
variables => updateMetadata({ variables }),
|
||||
variables => updatePrivateMetadata({ variables })
|
||||
);
|
||||
const handleImageUpload = createImageUploadHandler(id, 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 { ProductUpdatePageSubmitData } from "@saleor/products/components/ProductUpdatePage";
|
||||
import { ProductDetails_product } from "@saleor/products/types/ProductDetails";
|
||||
import { ProductImageCreateVariables } from "@saleor/products/types/ProductImageCreate";
|
||||
import { ProductImageReorderVariables } from "@saleor/products/types/ProductImageReorder";
|
||||
import { ProductUpdateVariables } from "@saleor/products/types/ProductUpdate";
|
||||
import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate";
|
||||
import {
|
||||
ProductUpdate,
|
||||
ProductUpdateVariables
|
||||
} from "@saleor/products/types/ProductUpdate";
|
||||
import {
|
||||
SimpleProductUpdate,
|
||||
SimpleProductUpdateVariables
|
||||
} from "@saleor/products/types/SimpleProductUpdate";
|
||||
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
import { MutationFetchResult } from "react-apollo";
|
||||
import { arrayMove } from "react-sortable-hoc";
|
||||
|
||||
export function createUpdateHandler(
|
||||
product: ProductDetails_product,
|
||||
updateProduct: (variables: ProductUpdateVariables) => void,
|
||||
updateSimpleProduct: (variables: SimpleProductUpdateVariables) => void
|
||||
updateProduct: (
|
||||
variables: ProductUpdateVariables
|
||||
) => Promise<MutationFetchResult<ProductUpdate>>,
|
||||
updateSimpleProduct: (
|
||||
variables: SimpleProductUpdateVariables
|
||||
) => Promise<MutationFetchResult<SimpleProductUpdate>>
|
||||
) {
|
||||
return (data: ProductUpdatePageSubmitData) => {
|
||||
return async (data: ProductUpdatePageSubmitData) => {
|
||||
const productVariables: ProductUpdateVariables = {
|
||||
attributes: data.attributes.map(attribute => ({
|
||||
id: attribute.id,
|
||||
|
@ -36,10 +50,15 @@ export function createUpdateHandler(
|
|||
}
|
||||
};
|
||||
|
||||
let errors: Array<
|
||||
ProductErrorFragment | StockErrorFragment | BulkStockErrorFragment
|
||||
>;
|
||||
|
||||
if (product.productType.hasVariants) {
|
||||
updateProduct(productVariables);
|
||||
const result = await updateProduct(productVariables);
|
||||
errors = result.data.productUpdate.errors;
|
||||
} else {
|
||||
updateSimpleProduct({
|
||||
const result = await updateSimpleProduct({
|
||||
...productVariables,
|
||||
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||
deleteStocks: data.removeStocks,
|
||||
|
@ -51,7 +70,16 @@ export function createUpdateHandler(
|
|||
updateStocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
||||
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 { commonMessages } from "@saleor/intl";
|
||||
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 React, { useEffect, useState } from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
@ -67,6 +72,8 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
id: variantId
|
||||
}
|
||||
});
|
||||
const [updateMetadata] = useMetadataUpdate({});
|
||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||
|
||||
const [openModal] = createDialogActionHandlers<
|
||||
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 (
|
||||
<>
|
||||
<WindowTitle title={data?.productVariant?.name} />
|
||||
|
@ -158,25 +198,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
onBack={handleBack}
|
||||
onDelete={() => openModal("remove")}
|
||||
onImageSelect={handleImageSelect}
|
||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
onVariantClick={variantId => {
|
||||
navigate(productVariantEditUrl(productId, variantId));
|
||||
}}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -506,6 +506,12 @@ export enum MenuSortField {
|
|||
NAME = "NAME",
|
||||
}
|
||||
|
||||
export enum MetadataErrorCode {
|
||||
GRAPHQL_ERROR = "GRAPHQL_ERROR",
|
||||
INVALID = "INVALID",
|
||||
NOT_FOUND = "NOT_FOUND",
|
||||
}
|
||||
|
||||
export enum OrderAction {
|
||||
CAPTURE = "CAPTURE",
|
||||
MARK_AS_PAID = "MARK_AS_PAID",
|
||||
|
@ -1186,6 +1192,11 @@ export interface MenuSortingInput {
|
|||
field: MenuSortField;
|
||||
}
|
||||
|
||||
export interface MetadataInput {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface NameTranslationInput {
|
||||
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 { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo";
|
||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { MetadataItem } from "@saleor/fragments/types/MetadataItem";
|
||||
import { Node } from "@saleor/types";
|
||||
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||
|
||||
export function mapCountriesToChoices(
|
||||
countries: ShopInfo_shop_countries[]
|
||||
|
@ -20,3 +22,10 @@ export function mapNodeToChoice(
|
|||
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