From c59a7f4349e71b436588ed3f5453c12b18a47986 Mon Sep 17 00:00:00 2001 From: Jakub Majorek Date: Thu, 19 Nov 2020 11:57:13 +0100 Subject: [PATCH 1/2] [1516] Create product when all required fields are valid --- .../views/ProductCreate/ProductCreate.tsx | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/src/products/views/ProductCreate/ProductCreate.tsx b/src/products/views/ProductCreate/ProductCreate.tsx index a2474630c..669e59927 100644 --- a/src/products/views/ProductCreate/ProductCreate.tsx +++ b/src/products/views/ProductCreate/ProductCreate.tsx @@ -7,9 +7,11 @@ import useChannels from "@saleor/hooks/useChannels"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; +import { getMutationStatus } from "@saleor/misc"; import ProductCreatePage from "@saleor/products/components/ProductCreatePage"; import { useProductChannelListingUpdate, + useProductDeleteMutation, useProductVariantChannelListingUpdate, useVariantCreateMutation } from "@saleor/products/mutations"; @@ -37,6 +39,9 @@ export const ProductCreateView: React.FC = () => { const notify = useNotifier(); const shop = useShop(); const intl = useIntl(); + const [productCreateComplete, setProductCreateComplete] = React.useState( + false + ); const { loadMore: loadMoreCategories, search: searchCategory, @@ -100,14 +105,9 @@ export const ProductCreateView: React.FC = () => { navigate(productUrl(productId)); }; - const [updateChannels, updateChannelsOpts] = useProductChannelListingUpdate({ - onCompleted: data => { - const productId = data.productChannelListingUpdate.product.id; - if (productId) { - handleSuccess(productId); - } - } - }); + const [updateChannels, updateChannelsOpts] = useProductChannelListingUpdate( + {} + ); const [ updateVariantChannels, updateVariantChannelsOpts @@ -116,6 +116,7 @@ export const ProductCreateView: React.FC = () => { const handleBack = () => navigate(productListUrl()); const [productCreate, productCreateOpts] = useProductCreateMutation({}); + const [deleteProduct] = useProductDeleteMutation({}); const [ productVariantCreate, productVariantCreateOpts @@ -133,17 +134,44 @@ export const ProductCreateView: React.FC = () => { } }); - const handleSubmit = createMetadataCreateHandler( - createHandler( - productTypes, - variables => productCreate({ variables }), - variables => productVariantCreate({ variables }), - updateChannels, - updateVariantChannels - ), - updateMetadata, - updatePrivateMetadata - ); + const handleSubmit = async data => { + await createMetadataCreateHandler( + createHandler( + productTypes, + variables => productCreate({ variables }), + variables => productVariantCreate({ variables }), + updateChannels, + updateVariantChannels + ), + updateMetadata, + updatePrivateMetadata + )(data); + + setProductCreateComplete(true); + }; + + React.useEffect(() => { + const productId = productCreateOpts.data?.productCreate?.product?.id; + // INFO: All these mutations contain required fields in Product Create UI + const statuses = [ + getMutationStatus(productCreateOpts), + getMutationStatus(productVariantCreateOpts), + getMutationStatus(updateChannelsOpts) + ]; + + if (productId && productCreateComplete) { + if (statuses.every(s => s === "success")) { + handleSuccess(productId); + } else { + /* + INFO: This is a stop-gap solution, where we delete products that didn't meet all required data in the create form + A more robust solution would require merging create and update form into one to persist form state across redirects + */ + setProductCreateComplete(false); + deleteProduct({ variables: { id: productId } }); + } + } + }, [productCreateComplete]); return ( <> From aa9df18fa88d918debdfdda295573ae506900dff Mon Sep 17 00:00:00 2001 From: Jakub Majorek Date: Thu, 19 Nov 2020 13:20:36 +0100 Subject: [PATCH 2/2] [1516] Move product remove logic to create handler --- .../views/ProductCreate/ProductCreate.tsx | 29 +++++---------- src/products/views/ProductCreate/handlers.ts | 36 +++++++++++++++++-- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/products/views/ProductCreate/ProductCreate.tsx b/src/products/views/ProductCreate/ProductCreate.tsx index 669e59927..ce9893b02 100644 --- a/src/products/views/ProductCreate/ProductCreate.tsx +++ b/src/products/views/ProductCreate/ProductCreate.tsx @@ -7,7 +7,6 @@ import useChannels from "@saleor/hooks/useChannels"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; -import { getMutationStatus } from "@saleor/misc"; import ProductCreatePage from "@saleor/products/components/ProductCreatePage"; import { useProductChannelListingUpdate, @@ -135,41 +134,29 @@ export const ProductCreateView: React.FC = () => { }); const handleSubmit = async data => { - await createMetadataCreateHandler( + const result = await createMetadataCreateHandler( createHandler( productTypes, variables => productCreate({ variables }), variables => productVariantCreate({ variables }), updateChannels, - updateVariantChannels + updateVariantChannels, + deleteProduct ), updateMetadata, updatePrivateMetadata )(data); - setProductCreateComplete(true); + if (result) { + setProductCreateComplete(true); + } }; React.useEffect(() => { const productId = productCreateOpts.data?.productCreate?.product?.id; - // INFO: All these mutations contain required fields in Product Create UI - const statuses = [ - getMutationStatus(productCreateOpts), - getMutationStatus(productVariantCreateOpts), - getMutationStatus(updateChannelsOpts) - ]; - if (productId && productCreateComplete) { - if (statuses.every(s => s === "success")) { - handleSuccess(productId); - } else { - /* - INFO: This is a stop-gap solution, where we delete products that didn't meet all required data in the create form - A more robust solution would require merging create and update form into one to persist form state across redirects - */ - setProductCreateComplete(false); - deleteProduct({ variables: { id: productId } }); - } + if (productCreateComplete && productId) { + handleSuccess(productId); } }, [productCreateComplete]); diff --git a/src/products/views/ProductCreate/handlers.ts b/src/products/views/ProductCreate/handlers.ts index 024033cb4..96e26f8b5 100644 --- a/src/products/views/ProductCreate/handlers.ts +++ b/src/products/views/ProductCreate/handlers.ts @@ -9,6 +9,10 @@ import { ProductCreate, ProductCreateVariables } from "@saleor/products/types/ProductCreate"; +import { + ProductDelete, + ProductDeleteVariables +} from "@saleor/products/types/ProductDelete"; import { ProductVariantChannelListingUpdate, ProductVariantChannelListingUpdateVariables @@ -59,7 +63,10 @@ export function createHandler( }) => Promise>, updateVariantChannels: (options: { variables: ProductVariantChannelListingUpdateVariables; - }) => Promise> + }) => Promise>, + productDelete: (options: { + variables: ProductDeleteVariables; + }) => Promise> ) { return async (formData: ProductCreateData) => { const productVariables: ProductCreateVariables = { @@ -85,6 +92,7 @@ export function createHandler( }; const result = await productCreate(productVariables); + let hasErrors = false; const hasVariants = productTypes.find( product => product.id === formData.productType.id @@ -98,7 +106,13 @@ export function createHandler( ), productVariantCreate(getSimpleProductVariables(formData, productId)) ]); - const variantErrors = result[1].data.productVariantCreate.errors; + const channelErrors = result[0].data?.productChannelListingUpdate?.errors; + const variantErrors = result[1].data?.productVariantCreate?.errors; + + if ([...(channelErrors || []), ...(variantErrors || [])].length > 0) { + hasErrors = true; + } + const variantId = result[1].data.productVariantCreate.productVariant?.id; if (variantErrors.length === 0 && variantId) { updateVariantChannels({ @@ -113,7 +127,23 @@ export function createHandler( }); } } else { - updateChannels(getChannelsVariables(productId, formData.channelListings)); + const result = await updateChannels( + getChannelsVariables(productId, formData.channelListings) + ); + + if (result.data?.productChannelListingUpdate?.errors.length > 0) { + hasErrors = true; + } + } + + /* + INFO: This is a stop-gap solution, where we delete products that didn't meet all required data in the create form + A more robust solution would require merging create and update form into one to persist form state across redirects + */ + if (productId && hasErrors) { + productDelete({ variables: { id: productId } }); + + return null; } return productId || null; };