From 78bb07e948a96d71914b5cc517b7224e2c4d2b15 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 21 Oct 2020 11:32:06 +0200 Subject: [PATCH] Extarct variant create form component --- .../ProductUpdatePage/ProductUpdatePage.tsx | 2 +- .../ProductVariantCreatePage.tsx | 253 ++++++------------ .../ProductVariantCreatePage/form.tsx | 141 ++++++++++ src/products/utils/data.ts | 18 +- src/products/views/ProductVariantCreate.tsx | 7 +- 5 files changed, 240 insertions(+), 181 deletions(-) create mode 100644 src/products/components/ProductVariantCreatePage/form.tsx diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index 9a6a6cf77..67d02dca1 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -61,13 +61,13 @@ export interface ProductUpdatePageProps extends ListActions { onVariantShow: (id: string) => () => void; onVariantReorder: ReorderAction; onImageDelete: (id: string) => () => void; + onSubmit: (data: ProductUpdatePageSubmitData) => Promise; onBack?(); onDelete(); onImageEdit?(id: string); onImageReorder?(event: { oldIndex: number; newIndex: number }); onImageUpload(file: File); onSeoClick?(); - onSubmit?(data: ProductUpdatePageSubmitData); onVariantAdd?(); onSetDefaultVariant(variant: ProductDetails_product_variants); onWarehouseConfigure(); diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index 4655ee737..cbe8fa284 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -2,48 +2,23 @@ import AppHeader from "@saleor/components/AppHeader"; import CardSpacer from "@saleor/components/CardSpacer"; 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, { MetadataFormData } from "@saleor/components/Metadata"; +import Metadata from "@saleor/components/Metadata"; import PageHeader from "@saleor/components/PageHeader"; import SaveButtonBar from "@saleor/components/SaveButtonBar"; import { ProductErrorWithAttributesFragment } from "@saleor/fragments/types/ProductErrorWithAttributesFragment"; -import useFormset, { - FormsetChange, - FormsetData -} from "@saleor/hooks/useFormset"; -import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; import { ReorderAction } from "@saleor/types"; -import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; import React from "react"; import { useIntl } from "react-intl"; -import { maybe } from "../../../misc"; import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData"; import ProductShipping from "../ProductShipping/ProductShipping"; -import ProductStocks, { ProductStockInput } from "../ProductStocks"; -import ProductVariantAttributes, { - VariantAttributeInputData -} from "../ProductVariantAttributes"; +import ProductStocks from "../ProductStocks"; +import ProductVariantAttributes from "../ProductVariantAttributes"; import ProductVariantNavigation from "../ProductVariantNavigation"; import ProductVariantPrice from "../ProductVariantPrice"; - -interface ProductVariantCreatePageFormData extends MetadataFormData { - costPrice: string; - images: string[]; - price: string; - quantity: string; - sku: string; - trackInventory: boolean; - weight: string; -} - -export interface ProductVariantCreatePageSubmitData - extends ProductVariantCreatePageFormData { - attributes: FormsetData; - stocks: ProductStockInput[]; -} +import ProductVariantCreateForm, { ProductVariantCreateData } from "./form"; interface ProductVariantCreatePageProps { currencySymbol: string; @@ -55,7 +30,7 @@ interface ProductVariantCreatePageProps { warehouses: SearchWarehouses_search_edges_node[]; weightUnit: string; onBack: () => void; - onSubmit: (data: ProductVariantCreatePageSubmitData) => void; + onSubmit: (data: ProductVariantCreateData) => void; onVariantClick: (variantId: string) => void; onVariantReorder: ReorderAction; onWarehouseConfigure: () => void; @@ -77,144 +52,90 @@ const ProductVariantCreatePage: React.FC = ({ onWarehouseConfigure }) => { const intl = useIntl(); - const attributeInput = React.useMemo( - () => getVariantAttributeInputFromProduct(product), - [product] - ); - const { change: changeAttributeData, data: attributes } = useFormset( - attributeInput - ); - const { - add: addStock, - change: changeStockData, - data: stocks, - remove: removeStock - } = useFormset([]); - const { - makeChangeHandler: makeMetadataChangeHandler - } = useMetadataChangeTrigger(); - - const initialForm: ProductVariantCreatePageFormData = { - costPrice: "", - images: product?.images.map(image => image.id), - metadata: [], - price: "", - privateMetadata: [], - quantity: "0", - sku: "", - trackInventory: true, - weight: "" - }; - - const handleSubmit = (data: ProductVariantCreatePageFormData) => - onSubmit({ - ...data, - attributes, - stocks - }); return ( -
- {({ change, data, hasChanged, submit, triggerChange }) => { - const handleAttributeChange: FormsetChange = (id, value) => { - changeAttributeData(id, value); - triggerChange(); - }; - const changeMetadata = makeMetadataChangeHandler(change); - - return ( - - {maybe(() => product.name)} - - -
- product.thumbnail.url)} - variants={maybe(() => product.variants)} - onRowClick={(variantId: string) => { - if (product && product.variants) { - return onVariantClick(variantId); - } - }} - onReorder={onVariantReorder} - /> -
-
- - - - - - - { - triggerChange(); - changeStockData(id, value); - }} - onWarehouseStockAdd={id => { - triggerChange(); - addStock({ - data: null, - id, - label: warehouses.find(warehouse => warehouse.id === id) - .name, - value: "0" - }); - }} - onWarehouseStockDelete={id => { - triggerChange(); - removeStock(id); - }} - onWarehouseConfigure={onWarehouseConfigure} - /> - - -
-
- -
- ); - }} -
+ + {({ change, data, handlers, hasChanged, submit }) => ( + + {product?.name} + + +
+ { + if (product && product.variants) { + return onVariantClick(variantId); + } + }} + onReorder={onVariantReorder} + /> +
+
+ + + + + + + + + +
+
+ +
+ )} +
); }; ProductVariantCreatePage.displayName = "ProductVariantCreatePage"; diff --git a/src/products/components/ProductVariantCreatePage/form.tsx b/src/products/components/ProductVariantCreatePage/form.tsx new file mode 100644 index 000000000..5e7e443e8 --- /dev/null +++ b/src/products/components/ProductVariantCreatePage/form.tsx @@ -0,0 +1,141 @@ +import { MetadataFormData } from "@saleor/components/Metadata"; +import useForm, { FormChange } from "@saleor/hooks/useForm"; +import useFormset, { + FormsetChange, + FormsetData +} from "@saleor/hooks/useFormset"; +import { ProductVariantCreateData_product } from "@saleor/products/types/ProductVariantCreateData"; +import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; +import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger"; +import React from "react"; + +import { ProductStockInput } from "../ProductStocks"; +import { VariantAttributeInputData } from "../ProductVariantAttributes"; + +export interface ProductVariantCreateFormData extends MetadataFormData { + costPrice: string; + price: string; + quantity: string; + sku: string; + trackInventory: boolean; + weight: string; +} +export interface ProductVariantCreateData extends ProductVariantCreateFormData { + attributes: FormsetData; + stocks: ProductStockInput[]; +} + +export interface UseProductVariantCreateFormOpts { + warehouses: SearchWarehouses_search_edges_node[]; +} + +export interface UseProductVariantCreateFormResult { + change: FormChange; + data: ProductVariantCreateData; + handlers: Record<"changeStock" | "selectAttribute", FormsetChange> & + Record<"addStock" | "deleteStock", (id: string) => void> & { + changeMetadata: FormChange; + }; + hasChanged: boolean; + submit: () => void; +} + +export interface ProductVariantCreateFormProps + extends UseProductVariantCreateFormOpts { + children: (props: UseProductVariantCreateFormResult) => React.ReactNode; + product: ProductVariantCreateData_product; + onSubmit: (data: ProductVariantCreateData) => void; +} + +const initial: ProductVariantCreateFormData = { + costPrice: "", + metadata: [], + price: "", + privateMetadata: [], + quantity: "0", + sku: "", + trackInventory: true, + weight: "" +}; + +function useProductVariantCreateForm( + product: ProductVariantCreateData_product, + onSubmit: (data: ProductVariantCreateData) => void, + opts: UseProductVariantCreateFormOpts +): UseProductVariantCreateFormResult { + const [changed, setChanged] = React.useState(false); + const triggerChange = () => setChanged(true); + + const attributeInput = getVariantAttributeInputFromProduct(product); + + const form = useForm(initial); + const attributes = useFormset(attributeInput); + const stocks = useFormset([]); + const { + makeChangeHandler: makeMetadataChangeHandler + } = useMetadataChangeTrigger(); + + const handleChange: FormChange = (event, cb) => { + form.change(event, cb); + triggerChange(); + }; + const changeMetadata = makeMetadataChangeHandler(handleChange); + const handleAttributeChange: FormsetChange = (id, value) => { + attributes.change(id, value); + triggerChange(); + }; + const handleStockAdd = (id: string) => { + triggerChange(); + stocks.add({ + data: null, + id, + label: opts.warehouses.find(warehouse => warehouse.id === id).name, + value: "0" + }); + }; + const handleStockChange = (id: string, value: string) => { + triggerChange(); + stocks.change(id, value); + }; + const handleStockDelete = (id: string) => { + triggerChange(); + stocks.remove(id); + }; + + const data: ProductVariantCreateData = { + ...form.data, + attributes: attributes.data, + stocks: stocks.data + }; + + const submit = () => onSubmit(data); + + return { + change: handleChange, + data, + handlers: { + addStock: handleStockAdd, + changeMetadata, + changeStock: handleStockChange, + deleteStock: handleStockDelete, + selectAttribute: handleAttributeChange + }, + hasChanged: changed, + submit + }; +} + +const ProductVariantCreateForm: React.FC = ({ + children, + product, + onSubmit, + ...rest +}) => { + const props = useProductVariantCreateForm(product, onSubmit, rest); + + return
{children(props)}
; +}; + +ProductVariantCreateForm.displayName = "ProductVariantCreateForm"; +export default ProductVariantCreateForm; diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts index 270cb5784..f581aecac 100644 --- a/src/products/utils/data.ts +++ b/src/products/utils/data.ts @@ -123,16 +123,14 @@ export function getStockInputFromVariant( export function getVariantAttributeInputFromProduct( product: ProductVariantCreateData_product ): VariantAttributeInput[] { - return maybe(() => - product.productType.variantAttributes.map(attribute => ({ - data: { - values: attribute.values - }, - id: attribute.id, - label: attribute.name, - value: "" - })) - ); + return product?.productType?.variantAttributes?.map(attribute => ({ + data: { + values: attribute.values + }, + id: attribute.id, + label: attribute.name, + value: "" + })); } export function getStockInputFromProduct( diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index 91041ba4f..1b891c0e5 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -15,9 +15,8 @@ import React from "react"; import { useIntl } from "react-intl"; import { decimal, weight } from "../../misc"; -import ProductVariantCreatePage, { - ProductVariantCreatePageSubmitData -} from "../components/ProductVariantCreatePage"; +import ProductVariantCreatePage from "../components/ProductVariantCreatePage"; +import { ProductVariantCreateData } from "../components/ProductVariantCreatePage/form"; import { useProductVariantReorderMutation, useVariantCreateMutation @@ -84,7 +83,7 @@ export const ProductVariant: React.FC = ({ ); const handleBack = () => navigate(productUrl(productId)); - const handleCreate = async (formData: ProductVariantCreatePageSubmitData) => { + const handleCreate = async (formData: ProductVariantCreateData) => { const result = await variantCreate({ variables: { input: {