Add custom form component to product create

This commit is contained in:
dominik-zeglen 2020-10-16 15:42:57 +02:00
parent f128253c06
commit d192bcac34
6 changed files with 427 additions and 330 deletions

View file

@ -51,7 +51,7 @@ function handleRefresh<T extends FormData>(
function useForm<T extends FormData>( function useForm<T extends FormData>(
initial: T, initial: T,
onSubmit: (data: T) => void onSubmit?: (data: T) => void
): UseFormResult<T> { ): UseFormResult<T> {
const [hasChanged, setChanged] = useState(false); const [hasChanged, setChanged] = useState(false);
const [data, setData] = useStateFromProps(initial, { const [data, setData] = useStateFromProps(initial, {
@ -107,7 +107,9 @@ function useForm<T extends FormData>(
} }
function submit() { function submit() {
return onSubmit(data); if (typeof onSubmit === "function") {
onSubmit(data);
}
} }
function triggerChange() { function triggerChange() {

View file

@ -3,9 +3,8 @@ import AvailabilityCard from "@saleor/components/AvailabilityCard";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata"; import Metadata from "@saleor/components/Metadata";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
@ -13,70 +12,29 @@ import SeoForm from "@saleor/components/SeoForm";
import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment";
import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment"; import { TaxTypeFragment } from "@saleor/fragments/types/TaxTypeFragment";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import useFormset from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { import { getChoices } from "@saleor/products/utils/data";
getAttributeInputFromProductType,
getChoices,
ProductType
} from "@saleor/products/utils/data";
import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories";
import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections";
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes"; import { SearchProductTypes_search_edges_node } from "@saleor/searches/types/SearchProductTypes";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import { ContentState, convertToRaw } from "draft-js";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { FetchMoreProps } from "../../../types"; import { FetchMoreProps } from "../../../types";
import { import ProductAttributes from "../ProductAttributes";
createAttributeChangeHandler,
createAttributeMultiChangeHandler,
createProductTypeSelectHandler
} from "../../utils/handlers";
import ProductAttributes, {
ProductAttributeInput,
ProductAttributeInputData
} from "../ProductAttributes";
import ProductDetailsForm from "../ProductDetailsForm"; import ProductDetailsForm from "../ProductDetailsForm";
import ProductOrganization from "../ProductOrganization"; import ProductOrganization from "../ProductOrganization";
import ProductPricing from "../ProductPricing"; import ProductPricing from "../ProductPricing";
import ProductShipping from "../ProductShipping/ProductShipping"; import ProductShipping from "../ProductShipping/ProductShipping";
import ProductStocks, { ProductStockInput } from "../ProductStocks"; import ProductStocks from "../ProductStocks";
import ProductTaxes from "../ProductTaxes"; import ProductTaxes from "../ProductTaxes";
import ProductCreateForm, {
interface FormData extends MetadataFormData { ProductCreateData,
availableForPurchase: string; ProductCreateFormData
basePrice: number; } from "./form";
category: string;
changeTaxCode: boolean;
chargeTaxes: boolean;
collections: string[];
description: RawDraftContentState;
isAvailable: boolean;
isAvailableForPurchase: boolean;
isPublished: boolean;
name: string;
slug: string;
productType: string;
publicationDate: string;
seoDescription: string;
seoTitle: string;
sku: string;
stockQuantity: number;
taxCode: string;
trackInventory: boolean;
visibleInListings: boolean;
weight: string;
}
export interface ProductCreatePageSubmitData extends FormData {
attributes: ProductAttributeInput[];
stocks: ProductStockInput[];
}
interface ProductCreatePageProps { interface ProductCreatePageProps {
errors: ProductErrorWithAttributesFragment[]; errors: ProductErrorWithAttributesFragment[];
@ -87,13 +45,8 @@ interface ProductCreatePageProps {
fetchMoreCategories: FetchMoreProps; fetchMoreCategories: FetchMoreProps;
fetchMoreCollections: FetchMoreProps; fetchMoreCollections: FetchMoreProps;
fetchMoreProductTypes: FetchMoreProps; fetchMoreProductTypes: FetchMoreProps;
initial?: Partial<FormData>; initial?: Partial<ProductCreateFormData>;
productTypes?: Array<{ productTypes?: SearchProductTypes_search_edges_node[];
id: string;
name: string;
hasVariants: boolean;
productAttributes: SearchProductTypes_search_edges_node_productAttributes[];
}>;
header: string; header: string;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
weightUnit: string; weightUnit: string;
@ -104,7 +57,7 @@ interface ProductCreatePageProps {
fetchProductTypes: (data: string) => void; fetchProductTypes: (data: string) => void;
onWarehouseConfigure: () => void; onWarehouseConfigure: () => void;
onBack?(); onBack?();
onSubmit?(data: ProductCreatePageSubmitData); onSubmit?(data: ProductCreateData);
} }
export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
@ -133,65 +86,12 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const initialProductType = productTypeChoiceList?.find(
productType => initial?.productType === productType.id
);
// Form values
const {
change: changeAttributeData,
data: attributes,
set: setAttributeData
} = useFormset<ProductAttributeInputData>(
initial?.productType
? getAttributeInputFromProductType(initialProductType)
: []
);
const {
add: addStock,
change: changeStockData,
data: stocks,
remove: removeStock
} = useFormset<null, string>([]);
// Ensures that it will not change after component rerenders, because it // Ensures that it will not change after component rerenders, because it
// generates different block keys and it causes editor to lose its content. // generates different block keys and it causes editor to lose its content.
const initialDescription = React.useRef( const initialDescription = React.useRef(
convertToRaw(ContentState.createFromText("")) convertToRaw(ContentState.createFromText(""))
); );
const {
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const initialData: FormData = {
...(initial || {}),
availableForPurchase: "",
basePrice: 0,
category: "",
changeTaxCode: false,
chargeTaxes: false,
collections: [],
description: {} as any,
isAvailable: false,
isAvailableForPurchase: false,
isPublished: false,
metadata: [],
name: "",
privateMetadata: [],
productType: "",
publicationDate: "",
seoDescription: "",
seoTitle: "",
sku: null,
slug: "",
stockQuantity: null,
taxCode: null,
trackInventory: false,
visibleInListings: false,
weight: ""
};
// Display values // Display values
const [selectedCategory, setSelectedCategory] = useStateFromProps( const [selectedCategory, setSelectedCategory] = useStateFromProps(
initial?.category || "" initial?.category || ""
@ -201,9 +101,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
MultiAutocompleteChoiceType[] MultiAutocompleteChoiceType[]
>([]); >([]);
const [productType, setProductType] = useStateFromProps<ProductType>(
initialProductType || null
);
const [selectedTaxType, setSelectedTaxType] = useStateFromProps( const [selectedTaxType, setSelectedTaxType] = useStateFromProps(
initial?.taxCode || null initial?.taxCode || null
); );
@ -217,214 +114,166 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
value: taxType.taxCode value: taxType.taxCode
})) || []; })) || [];
const handleSubmit = (data: FormData) =>
onSubmit({
...data,
attributes,
stocks
});
return ( return (
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave> <ProductCreateForm
{({ change, data, hasChanged, submit, triggerChange, toggleValue }) => { onSubmit={onSubmit}
const handleCollectionSelect = createMultiAutocompleteSelectHandler( initial={initial}
toggleValue, categories={categories}
setSelectedCollections, collections={collections}
selectedCollections, productTypes={productTypeChoiceList}
collections selectedCollections={selectedCollections}
); setSelectedCategory={setSelectedCategory}
const handleCategorySelect = createSingleAutocompleteSelectHandler( setSelectedCollections={setSelectedCollections}
change, setSelectedTaxType={setSelectedTaxType}
setSelectedCategory, taxTypes={taxTypeChoices}
categories warehouses={warehouses}
); >
const handleAttributeChange = createAttributeChangeHandler( {({ change, data, handlers, hasChanged, submit }) => (
changeAttributeData, <Container>
triggerChange <AppHeader onBack={onBack}>
); {intl.formatMessage(sectionNames.products)}
const handleAttributeMultiChange = createAttributeMultiChangeHandler( </AppHeader>
changeAttributeData, <PageHeader title={header} />
attributes, <Grid>
triggerChange <div>
); <ProductDetailsForm
data={data}
const handleProductTypeSelect = createProductTypeSelectHandler( disabled={disabled}
change, errors={errors}
setAttributeData, initialDescription={initialDescription.current}
setProductType, onChange={change}
productTypeChoiceList />
); <CardSpacer />
const handleTaxTypeSelect = createSingleAutocompleteSelectHandler( {data.attributes.length > 0 && (
change, <ProductAttributes
setSelectedTaxType, attributes={data.attributes}
taxTypeChoices
);
const changeMetadata = makeMetadataChangeHandler(change);
return (
<Container>
<AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.products)}
</AppHeader>
<PageHeader title={header} />
<Grid>
<div>
<ProductDetailsForm
data={data}
disabled={disabled} disabled={disabled}
errors={errors} errors={errors}
initialDescription={initialDescription.current} onChange={handlers.selectAttribute}
onChange={change} onMultiChange={handlers.selectAttributeMultiple}
/> />
<CardSpacer /> )}
{attributes.length > 0 && ( <CardSpacer />
<ProductAttributes {!data.productType?.hasVariants && (
attributes={attributes} <>
<ProductShipping
data={data}
disabled={disabled} disabled={disabled}
errors={errors} errors={errors}
onChange={handleAttributeChange} weightUnit={weightUnit}
onMultiChange={handleAttributeMultiChange} onChange={change}
/> />
)} <ProductPricing
<CardSpacer /> currency={currency}
{!!productType && !productType.hasVariants && ( data={data}
<> disabled={disabled}
<ProductShipping errors={errors}
data={data} onChange={change}
disabled={disabled} />
errors={errors} <CardSpacer />
weightUnit={weightUnit} <ProductStocks
onChange={change} data={data}
/> disabled={disabled}
<ProductPricing hasVariants={false}
currency={currency} onFormDataChange={change}
data={data} errors={errors}
disabled={disabled} stocks={data.stocks}
errors={errors} warehouses={warehouses}
onChange={change} onChange={handlers.changeStock}
/> onWarehouseStockAdd={handlers.addStock}
<CardSpacer /> onWarehouseStockDelete={handlers.deleteStock}
<ProductStocks onWarehouseConfigure={onWarehouseConfigure}
data={data} />
disabled={disabled} <CardSpacer />
hasVariants={false} </>
onFormDataChange={change} )}
errors={errors} <SeoForm
stocks={stocks} allowEmptySlug={true}
warehouses={warehouses} helperText={intl.formatMessage({
onChange={(id, value) => { defaultMessage:
triggerChange(); "Add search engine title and description to make this product easier to find"
changeStockData(id, value); })}
}} title={data.seoTitle}
onWarehouseStockAdd={id => { slug={data.slug}
triggerChange(); slugPlaceholder={data.name}
addStock({ titlePlaceholder={data.name}
data: null, description={data.seoDescription}
id, descriptionPlaceholder={data.seoTitle}
label: warehouses.find( loading={disabled}
warehouse => warehouse.id === id onChange={change}
).name, />
value: "0" <CardSpacer />
}); <Metadata data={data} onChange={handlers.changeMetadata} />
}} </div>
onWarehouseStockDelete={id => { <div>
triggerChange(); <ProductOrganization
removeStock(id); canChangeType={true}
}} categories={categories}
onWarehouseConfigure={onWarehouseConfigure} categoryInputDisplayValue={selectedCategory}
/> collections={collections}
<CardSpacer /> data={data}
</> disabled={disabled}
)} errors={errors}
<SeoForm fetchCategories={fetchCategories}
allowEmptySlug={true} fetchCollections={fetchCollections}
helperText={intl.formatMessage({ fetchMoreCategories={fetchMoreCategories}
defaultMessage: fetchMoreCollections={fetchMoreCollections}
"Add search engine title and description to make this product easier to find" fetchMoreProductTypes={fetchMoreProductTypes}
})} fetchProductTypes={fetchProductTypes}
title={data.seoTitle} productType={data.productType}
slug={data.slug} productTypeInputDisplayValue={data.productType?.name || ""}
slugPlaceholder={data.name} productTypes={productTypes}
titlePlaceholder={data.name} onCategoryChange={handlers.selectCategory}
description={data.seoDescription} onCollectionChange={handlers.selectCollection}
descriptionPlaceholder={data.seoTitle} onProductTypeChange={handlers.selectProductType}
loading={disabled} collectionsInputDisplayValue={selectedCollections}
onChange={change} />
/> <CardSpacer />
<CardSpacer /> <AvailabilityCard
<Metadata data={data} onChange={changeMetadata} /> data={data}
</div> errors={errors}
<div> disabled={disabled}
<ProductOrganization messages={{
canChangeType={true} hiddenLabel: intl.formatMessage({
categories={categories} defaultMessage: "Not published",
categoryInputDisplayValue={selectedCategory} description: "product label"
collections={collections} }),
data={data} hiddenSecondLabel: intl.formatMessage(
disabled={disabled} {
errors={errors} defaultMessage: "will become published on {date}",
fetchCategories={fetchCategories} description: "product publication date label"
fetchCollections={fetchCollections} },
fetchMoreCategories={fetchMoreCategories} {
fetchMoreCollections={fetchMoreCollections} date: localizeDate(data.publicationDate, "L")
fetchMoreProductTypes={fetchMoreProductTypes} }
fetchProductTypes={fetchProductTypes} ),
productType={productType} visibleLabel: intl.formatMessage({
productTypeInputDisplayValue={productType?.name || ""} defaultMessage: "Published",
productTypes={productTypes} description: "product label"
onCategoryChange={handleCategorySelect} })
onCollectionChange={handleCollectionSelect} }}
onProductTypeChange={handleProductTypeSelect} onChange={change}
collectionsInputDisplayValue={selectedCollections} />
/> <CardSpacer />
<CardSpacer /> <ProductTaxes
<AvailabilityCard data={data}
data={data} disabled={disabled}
errors={errors} onChange={change}
disabled={disabled} onTaxTypeChange={handlers.selectTaxRate}
messages={{ selectedTaxTypeDisplayName={selectedTaxType}
hiddenLabel: intl.formatMessage({ taxTypes={taxTypes}
defaultMessage: "Not published", />
description: "product label" </div>
}), </Grid>
hiddenSecondLabel: intl.formatMessage( <SaveButtonBar
{ onCancel={onBack}
defaultMessage: "will become published on {date}", onSave={submit}
description: "product publication date label" state={saveButtonBarState}
}, disabled={disabled || !onSubmit || !hasChanged}
{ />
date: localizeDate(data.publicationDate, "L") </Container>
} )}
), </ProductCreateForm>
visibleLabel: intl.formatMessage({
defaultMessage: "Published",
description: "product label"
})
}}
onChange={change}
/>
<CardSpacer />
<ProductTaxes
data={data}
disabled={disabled}
onChange={change}
onTaxTypeChange={handleTaxTypeSelect}
selectedTaxTypeDisplayName={selectedTaxType}
taxTypes={taxTypes}
/>
</div>
</Grid>
<SaveButtonBar
onCancel={onBack}
onSave={submit}
state={saveButtonBarState}
disabled={disabled || !onSubmit || !hasChanged}
/>
</Container>
);
}}
</Form>
); );
}; };
ProductCreatePage.displayName = "ProductCreatePage"; ProductCreatePage.displayName = "ProductCreatePage";

View file

@ -0,0 +1,248 @@
import { MetadataFormData } from "@saleor/components/Metadata";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
import useForm, { FormChange } from "@saleor/hooks/useForm";
import useFormset, { FormsetChange } from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import {
getAttributeInputFromProductType,
ProductType
} from "@saleor/products/utils/data";
import {
createAttributeChangeHandler,
createAttributeMultiChangeHandler,
createProductTypeSelectHandler
} from "@saleor/products/utils/handlers";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
import { RawDraftContentState } from "draft-js";
import React from "react";
import { SearchProductTypes_search_edges_node } from "../../../searches/types/SearchProductTypes";
import {
ProductAttributeInput,
ProductAttributeInputData
} from "../ProductAttributes";
import { ProductStockInput } from "../ProductStocks";
export interface ProductCreateFormData extends MetadataFormData {
availableForPurchase: string;
basePrice: number;
category: string;
changeTaxCode: boolean;
chargeTaxes: boolean;
collections: string[];
description: RawDraftContentState;
isAvailable: boolean;
isAvailableForPurchase: boolean;
isPublished: boolean;
name: string;
productType: ProductType;
publicationDate: string;
seoDescription: string;
seoTitle: string;
sku: string;
slug: string;
stockQuantity: number;
taxCode: string;
trackInventory: boolean;
visibleInListings: boolean;
weight: string;
}
export interface ProductCreateData extends ProductCreateFormData {
attributes: ProductAttributeInput[];
stocks: ProductStockInput[];
}
export interface UseProductCreateFormResult {
change: FormChange;
data: ProductCreateData;
handlers: Record<
| "changeMetadata"
| "selectCategory"
| "selectCollection"
| "selectProductType"
| "selectTaxRate",
FormChange
> &
Record<
"changeStock" | "selectAttribute" | "selectAttributeMultiple",
FormsetChange
> &
Record<"addStock" | "deleteStock", (id: string) => void>;
hasChanged: boolean;
submit: () => Promise<boolean>;
}
export interface UseProductCreateFormOpts
extends Record<
"categories" | "collections" | "taxTypes",
SingleAutocompleteChoiceType[]
> {
setSelectedCategory: React.Dispatch<React.SetStateAction<string>>;
setSelectedCollections: React.Dispatch<
React.SetStateAction<MultiAutocompleteChoiceType[]>
>;
setSelectedTaxType: React.Dispatch<React.SetStateAction<string>>;
selectedCollections: MultiAutocompleteChoiceType[];
productTypes: SearchProductTypes_search_edges_node[];
warehouses: SearchWarehouses_search_edges_node[];
}
export interface ProductCreateFormProps extends UseProductCreateFormOpts {
children: (props: UseProductCreateFormResult) => React.ReactNode;
initial?: Partial<ProductCreateFormData>;
onSubmit: (data: ProductCreateData) => Promise<boolean>;
}
const defaultInitialFormData: ProductCreateFormData &
Record<"productType", string> = {
availableForPurchase: "",
basePrice: 0,
category: "",
changeTaxCode: false,
chargeTaxes: false,
collections: [],
description: {} as any,
isAvailable: false,
isAvailableForPurchase: false,
isPublished: false,
metadata: [],
name: "",
privateMetadata: [],
productType: null,
publicationDate: "",
seoDescription: "",
seoTitle: "",
sku: null,
slug: "",
stockQuantity: null,
taxCode: null,
trackInventory: false,
visibleInListings: false,
weight: ""
};
function useProductCreateForm(
initial: Partial<ProductCreateFormData>,
onSubmit: (data: ProductCreateData) => Promise<boolean>,
opts: UseProductCreateFormOpts
): UseProductCreateFormResult {
const initialProductType =
opts.productTypes?.find(
productType => initial?.productType?.id === productType.id
) || null;
const [changed, setChanged] = React.useState(false);
const triggerChange = () => setChanged(true);
const form = useForm({
...initial,
...defaultInitialFormData
});
const attributes = useFormset<ProductAttributeInputData>(
initial?.productType
? getAttributeInputFromProductType(initialProductType)
: []
);
const stocks = useFormset<null, string>([]);
const [productType, setProductType] = useStateFromProps<ProductType>(
initialProductType || null
);
const {
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const handleCollectionSelect = createMultiAutocompleteSelectHandler(
form.toggleValue,
opts.setSelectedCollections,
opts.selectedCollections,
opts.collections
);
const handleCategorySelect = createSingleAutocompleteSelectHandler(
form.change,
opts.setSelectedCategory,
opts.categories
);
const handleAttributeChange = createAttributeChangeHandler(
attributes.change,
triggerChange
);
const handleAttributeMultiChange = createAttributeMultiChangeHandler(
attributes.change,
attributes.data,
triggerChange
);
const handleProductTypeSelect = createProductTypeSelectHandler(
attributes.set,
setProductType,
opts.productTypes,
triggerChange
);
const handleStockChange: FormsetChange = (id, value) => {
triggerChange();
stocks.change(id, value);
};
const handleStockAdd = (id: string) => {
triggerChange();
stocks.add({
data: null,
id,
label: opts.warehouses.find(warehouse => warehouse.id === id).name,
value: "0"
});
};
const handleStockDelete = (id: string) => {
triggerChange();
stocks.remove(id);
};
const handleTaxTypeSelect = createSingleAutocompleteSelectHandler(
form.change,
opts.setSelectedTaxType,
opts.taxTypes
);
const changeMetadata = makeMetadataChangeHandler(form.change);
const data: ProductCreateData = {
...form.data,
attributes: attributes.data,
productType,
stocks: stocks.data
};
const submit = () => onSubmit(data);
return {
change: form.change,
data,
handlers: {
addStock: handleStockAdd,
changeMetadata,
changeStock: handleStockChange,
deleteStock: handleStockDelete,
selectAttribute: handleAttributeChange,
selectAttributeMultiple: handleAttributeMultiChange,
selectCategory: handleCategorySelect,
selectCollection: handleCollectionSelect,
selectProductType: handleProductTypeSelect,
selectTaxRate: handleTaxTypeSelect
},
hasChanged: changed,
submit
};
}
const ProductCreateForm: React.FC<ProductCreateFormProps> = ({
children,
initial,
onSubmit,
...rest
}) => {
const props = useProductCreateForm(initial || {}, onSubmit, rest);
return <form onSubmit={props.submit}>{children(props)}</form>;
};
ProductCreateForm.displayName = "ProductCreateForm";
export default ProductCreateForm;

View file

@ -51,7 +51,7 @@ interface ProductOrganizationProps {
data: { data: {
category: string; category: string;
collections: string[]; collections: string[];
productType?: string; productType?: ProductType;
}; };
disabled: boolean; disabled: boolean;
errors: ProductErrorFragment[]; errors: ProductErrorFragment[];
@ -121,7 +121,7 @@ const ProductOrganization: React.FC<ProductOrganizationProps> = props => {
defaultMessage: "Product Type" defaultMessage: "Product Type"
})} })}
choices={productTypes} choices={productTypes}
value={data.productType} value={data.productType?.id}
onChange={onProductTypeChange} onChange={onProductTypeChange}
fetchChoices={fetchProductTypes} fetchChoices={fetchProductTypes}
data-test="product-type" data-test="product-type"

View file

@ -37,19 +37,18 @@ export function createAttributeMultiChangeHandler(
} }
export function createProductTypeSelectHandler( export function createProductTypeSelectHandler(
change: FormChange,
setAttributes: (data: FormsetData<ProductAttributeInputData>) => void, setAttributes: (data: FormsetData<ProductAttributeInputData>) => void,
setProductType: (productType: ProductType) => void, setProductType: (productType: ProductType) => void,
productTypeChoiceList: ProductType[] productTypeChoiceList: ProductType[],
triggerChange: () => void
): FormChange { ): FormChange {
return (event: React.ChangeEvent<any>) => { return (event: React.ChangeEvent<any>) => {
const id = event.target.value; const id = event.target.value;
const selectedProductType = productTypeChoiceList.find( const selectedProductType = productTypeChoiceList.find(
productType => productType.id === id productType => productType.id === id
); );
triggerChange();
setProductType(selectedProductType); setProductType(selectedProductType);
change(event);
setAttributes(getAttributeInputFromProductType(selectedProductType)); setAttributes(getAttributeInputFromProductType(selectedProductType));
}; };
} }

View file

@ -19,9 +19,8 @@ import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { decimal, weight } from "../../misc"; import { decimal, weight } from "../../misc";
import ProductCreatePage, { import ProductCreatePage from "../components/ProductCreatePage";
ProductCreatePageSubmitData import { ProductCreateData } from "../components/ProductCreatePage/form";
} from "../components/ProductCreatePage";
import { import {
useProductCreateMutation, useProductCreateMutation,
useProductSetAvailabilityForPurchase useProductSetAvailabilityForPurchase
@ -91,7 +90,7 @@ export const ProductCreateView: React.FC = () => {
} }
}); });
const handleCreate = async (formData: ProductCreatePageSubmitData) => { const handleCreate = async (formData: ProductCreateData) => {
const result = await productCreate({ const result = await productCreate({
variables: { variables: {
input: { input: {
@ -106,7 +105,7 @@ export const ProductCreateView: React.FC = () => {
descriptionJson: JSON.stringify(formData.description), descriptionJson: JSON.stringify(formData.description),
isPublished: formData.isPublished, isPublished: formData.isPublished,
name: formData.name, name: formData.name,
productType: formData.productType, productType: formData.productType?.id,
publicationDate: publicationDate:
formData.publicationDate !== "" ? formData.publicationDate : null, formData.publicationDate !== "" ? formData.publicationDate : null,
seo: { seo: {