diff --git a/CHANGELOG.md b/CHANGELOG.md index 346aa6dfa..18e1c10fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ All notable, unreleased changes to this project will be documented in this file. - Add warehouse choice - #646 by @dominik-zeglen - Fix user management modal actions - #637 by @eaglesemanation - 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 ## 2.10.1 diff --git a/src/categories/views/CategoryDetails.tsx b/src/categories/views/CategoryDetails.tsx index bdd3c13e6..5dc0d750f 100644 --- a/src/categories/views/CategoryDetails.tsx +++ b/src/categories/views/CategoryDetails.tsx @@ -17,8 +17,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { PAGINATE_BY } from "../../config"; import { maybe } from "../../misc"; -import { TypedProductBulkDeleteMutation } from "../../products/mutations"; -import { productBulkDelete } from "../../products/types/productBulkDelete"; +import { useProductBulkDeleteMutation } from "../../products/mutations"; import { productAddUrl, productUrl } from "../../products/urls"; import { CategoryInput } from "../../types/globalTypes"; import { @@ -129,6 +128,23 @@ export const CategoryDetails: React.FC = ({ onCompleted: handleBulkCategoryDelete }); + const [ + productBulkDelete, + productBulkDeleteOpts + ] = useProductBulkDeleteMutation({ + onCompleted: data => { + if (data.productBulkDelete.errors.length === 0) { + closeModal(); + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + refetch(); + reset(); + } + } + }); + const changeTab = (tabName: CategoryPageTab) => { reset(); navigate( @@ -143,18 +159,6 @@ export const CategoryDetails: React.FC = ({ CategoryUrlQueryParams >(navigate, params => categoryUrl(id, params), params); - const handleBulkProductDelete = (data: productBulkDelete) => { - if (data.productBulkDelete.errors.length === 0) { - closeModal(); - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - refetch(); - reset(); - } - }; - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( params.activeTab === CategoryPageTab.categories ? maybe(() => data.category.children.pageInfo) @@ -166,191 +170,178 @@ export const CategoryDetails: React.FC = ({ return ( <> data.category.name)} /> - - {(productBulkDelete, productBulkDeleteOpts) => ( - <> - data.category)} - disabled={loading} - errors={updateResult.data?.categoryUpdate.errors || []} - onAddCategory={() => navigate(categoryAddUrl(id))} - onAddProduct={() => navigate(productAddUrl)} - onBack={() => - navigate( - maybe( - () => categoryUrl(data.category.parent.id), - categoryListUrl() - ) - ) + data.category)} + disabled={loading} + errors={updateResult.data?.categoryUpdate.errors || []} + onAddCategory={() => navigate(categoryAddUrl(id))} + onAddProduct={() => navigate(productAddUrl)} + onBack={() => + navigate( + maybe(() => categoryUrl(data.category.parent.id), categoryListUrl()) + ) + } + onCategoryClick={id => () => navigate(categoryUrl(id))} + onDelete={() => openModal("delete")} + onImageDelete={() => + updateCategory({ + variables: { + id, + input: { + backgroundImage: null } - onCategoryClick={id => () => navigate(categoryUrl(id))} - onDelete={() => openModal("delete")} - onImageDelete={() => - updateCategory({ - variables: { - id, - input: { - backgroundImage: null - } - } - }) + } + }) + } + onImageUpload={file => + updateCategory({ + variables: { + id, + input: { + backgroundImage: file } - onImageUpload={file => - updateCategory({ - variables: { - id, - input: { - backgroundImage: file - } - } - }) + } + }) + } + onNextPage={loadNextPage} + 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 + } } - onNextPage={loadNextPage} - 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 - } - } - } - }) - } - products={maybe(() => - data.category.products.edges.map(edge => edge.node) - )} - saveButtonBarState={updateResult.status} - subcategories={maybe(() => - data.category.children.edges.map(edge => edge.node) - )} - subcategoryListToolbar={ - - openModal("delete-categories", { - ids: listElements - }) - } - > - - - } - productListToolbar={ - - openModal("delete-products", { - ids: listElements - }) - } - > - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - /> - deleteCategory({ variables: { id } })} - open={params.action === "delete"} - title={intl.formatMessage({ - defaultMessage: "Delete category", - description: "dialog title" - })} - variant="delete" - > - - {maybe(() => data.category.name, "...")} - ) - }} - /> - - - - - - params.ids.length > 0) - } - confirmButtonState={categoryBulkDeleteOpts.status} - onClose={closeModal} - onConfirm={() => - categoryBulkDelete({ - variables: { ids: params.ids } - }).then(() => refetch()) - } - title={intl.formatMessage({ - defaultMessage: "Delete categories", - description: "dialog title" - })} - variant="delete" - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - - - - productBulkDelete({ - variables: { ids: params.ids } - }).then(() => refetch()) - } - title={intl.formatMessage({ - defaultMessage: "Delete products", - description: "dialog title" - })} - variant="delete" - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - + } + }) + } + products={maybe(() => + data.category.products.edges.map(edge => edge.node) )} - + saveButtonBarState={updateResult.status} + subcategories={maybe(() => + data.category.children.edges.map(edge => edge.node) + )} + subcategoryListToolbar={ + + openModal("delete-categories", { + ids: listElements + }) + } + > + + + } + productListToolbar={ + + openModal("delete-products", { + ids: listElements + }) + } + > + + + } + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + /> + deleteCategory({ variables: { id } })} + open={params.action === "delete"} + title={intl.formatMessage({ + defaultMessage: "Delete category", + description: "dialog title" + })} + variant="delete" + > + + {maybe(() => data.category.name, "...")} + ) + }} + /> + + + + + + params.ids.length > 0) + } + confirmButtonState={categoryBulkDeleteOpts.status} + onClose={closeModal} + onConfirm={() => + categoryBulkDelete({ + variables: { ids: params.ids } + }).then(() => refetch()) + } + title={intl.formatMessage({ + defaultMessage: "Delete categories", + description: "dialog title" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + + + + + + productBulkDelete({ + variables: { ids: params.ids } + }).then(() => refetch()) + } + title={intl.formatMessage({ + defaultMessage: "Delete products", + description: "dialog title" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + ); }; diff --git a/src/products/containers/ProductImagesReorder.tsx b/src/products/containers/ProductImagesReorder.tsx index c4f019d74..8fcbfd93a 100644 --- a/src/products/containers/ProductImagesReorder.tsx +++ b/src/products/containers/ProductImagesReorder.tsx @@ -1,7 +1,7 @@ import React from "react"; import { TypedMutationInnerProps } from "../../mutations"; -import { TypedProductImagesReorder } from "../mutations"; +import { useProductImagesReorder } from "../mutations"; import { ProductImageReorder, ProductImageReorderVariables @@ -24,10 +24,12 @@ const ProductImagesReorderProvider: React.FC productId, productImages, ...mutationProps -}) => ( - - {(mutate, mutationResult) => - children(opts => { +}) => { + const [mutate, mutationResult] = useProductImagesReorder(mutationProps); + + return ( + <> + {children(opts => { const productImagesMap = productImages.reduce((prev, curr) => { prev[curr.id] = curr; return prev; @@ -52,9 +54,9 @@ const ProductImagesReorderProvider: React.FC ...opts, optimisticResponse }); - }, mutationResult) - } - -); + }, mutationResult)} + + ); +}; export default ProductImagesReorderProvider; diff --git a/src/products/containers/ProductUpdateOperations.tsx b/src/products/containers/ProductUpdateOperations.tsx deleted file mode 100644 index a2e79a952..000000000 --- a/src/products/containers/ProductUpdateOperations.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import React from "react"; - -import { getMutationProviderData, maybe } from "../../misc"; -import { PartialMutationProviderOutput } from "../../types"; -import { - TypedProductDeleteMutation, - TypedProductImageCreateMutation, - TypedProductImageDeleteMutation, - TypedProductUpdateMutation, - TypedProductVariantBulkDeleteMutation, - TypedSimpleProductUpdateMutation -} from "../mutations"; -import { ProductDelete, ProductDeleteVariables } from "../types/ProductDelete"; -import { ProductDetails_product } from "../types/ProductDetails"; -import { - ProductImageCreate, - ProductImageCreateVariables -} from "../types/ProductImageCreate"; -import { - ProductImageDelete, - ProductImageDeleteVariables -} from "../types/ProductImageDelete"; -import { - ProductImageReorder, - ProductImageReorderVariables -} from "../types/ProductImageReorder"; -import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate"; -import { - ProductVariantBulkDelete, - ProductVariantBulkDeleteVariables -} from "../types/ProductVariantBulkDelete"; -import { - SimpleProductUpdate, - SimpleProductUpdateVariables -} from "../types/SimpleProductUpdate"; -import ProductImagesReorderProvider from "./ProductImagesReorder"; - -interface ProductUpdateOperationsProps { - product: ProductDetails_product; - children: (props: { - bulkProductVariantDelete: PartialMutationProviderOutput< - ProductVariantBulkDelete, - ProductVariantBulkDeleteVariables - >; - createProductImage: PartialMutationProviderOutput< - ProductImageCreate, - ProductImageCreateVariables - >; - deleteProduct: PartialMutationProviderOutput< - ProductDelete, - ProductDeleteVariables - >; - deleteProductImage: PartialMutationProviderOutput< - ProductImageDelete, - ProductImageDeleteVariables - >; - reorderProductImages: PartialMutationProviderOutput< - ProductImageReorder, - ProductImageReorderVariables - >; - updateProduct: PartialMutationProviderOutput< - ProductUpdate, - ProductUpdateVariables - >; - updateSimpleProduct: PartialMutationProviderOutput< - SimpleProductUpdate, - SimpleProductUpdateVariables - >; - }) => React.ReactNode; - onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void; - onDelete?: (data: ProductDelete) => void; - onImageCreate?: (data: ProductImageCreate) => void; - onImageDelete?: (data: ProductImageDelete) => void; - onImageReorder?: (data: ProductImageReorder) => void; - onUpdate?: (data: ProductUpdate) => void; -} - -const ProductUpdateOperations: React.FC = ({ - product, - children, - onBulkProductVariantDelete, - onDelete, - onImageDelete, - onImageCreate, - onImageReorder, - onUpdate -}) => { - const productId = product ? product.id : ""; - return ( - - {(...updateProduct) => ( - product.images, [])} - onCompleted={onImageReorder} - > - {(...reorderProductImages) => ( - - {(...createProductImage) => ( - - {(...deleteProduct) => ( - - {(...deleteProductImage) => ( - - {(...updateSimpleProduct) => ( - - {(...bulkProductVariantDelete) => - children({ - bulkProductVariantDelete: getMutationProviderData( - ...bulkProductVariantDelete - ), - createProductImage: getMutationProviderData( - ...createProductImage - ), - deleteProduct: getMutationProviderData( - ...deleteProduct - ), - deleteProductImage: getMutationProviderData( - ...deleteProductImage - ), - reorderProductImages: getMutationProviderData( - ...reorderProductImages - ), - updateProduct: getMutationProviderData( - ...updateProduct - ), - updateSimpleProduct: getMutationProviderData( - ...updateSimpleProduct - ) - }) - } - - )} - - )} - - )} - - )} - - )} - - )} - - ); -}; -export default ProductUpdateOperations; diff --git a/src/products/containers/ProductVariantOperations.tsx b/src/products/containers/ProductVariantOperations.tsx deleted file mode 100644 index fdb9a4a8b..000000000 --- a/src/products/containers/ProductVariantOperations.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from "react"; - -import { getMutationProviderData } from "../../misc"; -import { PartialMutationProviderOutput } from "../../types"; -import { - TypedVariantDeleteMutation, - TypedVariantImageAssignMutation, - TypedVariantImageUnassignMutation, - TypedVariantUpdateMutation -} from "../mutations"; -import { VariantDelete, VariantDeleteVariables } from "../types/VariantDelete"; -import { - VariantImageAssign, - VariantImageAssignVariables -} from "../types/VariantImageAssign"; -import { - VariantImageUnassign, - VariantImageUnassignVariables -} from "../types/VariantImageUnassign"; -import { VariantUpdate, VariantUpdateVariables } from "../types/VariantUpdate"; - -interface VariantDeleteOperationsProps { - children: (props: { - deleteVariant: PartialMutationProviderOutput< - VariantDelete, - VariantDeleteVariables - >; - updateVariant: PartialMutationProviderOutput< - VariantUpdate, - VariantUpdateVariables - >; - assignImage: PartialMutationProviderOutput< - VariantImageAssign, - VariantImageAssignVariables - >; - unassignImage: PartialMutationProviderOutput< - VariantImageUnassign, - VariantImageUnassignVariables - >; - }) => React.ReactNode; - onDelete?: (data: VariantDelete) => void; - onImageAssign?: (data: VariantImageAssign) => void; - onImageUnassign?: (data: VariantImageUnassign) => void; - onUpdate?: (data: VariantUpdate) => void; -} - -const VariantUpdateOperations: React.FC = ({ - children, - onDelete, - onUpdate, - onImageAssign, - onImageUnassign -}) => ( - - {(...assignImage) => ( - - {(...unassignImage) => ( - - {(...updateVariant) => ( - - {(...deleteVariant) => - children({ - assignImage: getMutationProviderData(...assignImage), - deleteVariant: getMutationProviderData(...deleteVariant), - unassignImage: getMutationProviderData(...unassignImage), - updateVariant: getMutationProviderData(...updateVariant) - }) - } - - )} - - )} - - )} - -); -export default VariantUpdateOperations; diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 7a8927b9e..87dbe40b4 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -13,7 +13,6 @@ import { import makeMutation from "@saleor/hooks/makeMutation"; import gql from "graphql-tag"; -import { TypedMutation } from "../mutations"; import { productBulkDelete, productBulkDeleteVariables @@ -80,7 +79,7 @@ export const productImageCreateMutation = gql` } } `; -export const TypedProductImageCreateMutation = TypedMutation< +export const useProductImageCreateMutation = makeMutation< ProductImageCreate, ProductImageCreateVariables >(productImageCreateMutation); @@ -98,7 +97,7 @@ export const productDeleteMutation = gql` } } `; -export const TypedProductDeleteMutation = TypedMutation< +export const useProductDeleteMutation = makeMutation< ProductDelete, ProductDeleteVariables >(productDeleteMutation); @@ -122,7 +121,7 @@ export const productImagesReorder = gql` } } `; -export const TypedProductImagesReorder = TypedMutation< +export const useProductImagesReorder = makeMutation< ProductImageReorder, ProductImageReorderVariables >(productImagesReorder); @@ -167,7 +166,7 @@ export const productUpdateMutation = gql` } } `; -export const TypedProductUpdateMutation = TypedMutation< +export const useProductUpdateMutation = makeMutation< ProductUpdate, ProductUpdateVariables >(productUpdateMutation); @@ -263,7 +262,7 @@ export const simpleProductUpdateMutation = gql` } } `; -export const TypedSimpleProductUpdateMutation = TypedMutation< +export const useSimpleProductUpdateMutation = makeMutation< SimpleProductUpdate, SimpleProductUpdateVariables >(simpleProductUpdateMutation); @@ -316,7 +315,7 @@ export const productCreateMutation = gql` } } `; -export const TypedProductCreateMutation = TypedMutation< +export const useProductCreateMutation = makeMutation< ProductCreate, ProductCreateVariables >(productCreateMutation); @@ -334,7 +333,7 @@ export const variantDeleteMutation = gql` } } `; -export const TypedVariantDeleteMutation = TypedMutation< +export const useVariantDeleteMutation = makeMutation< VariantDelete, VariantDeleteVariables >(variantDeleteMutation); @@ -406,7 +405,7 @@ export const variantUpdateMutation = gql` } } `; -export const TypedVariantUpdateMutation = TypedMutation< +export const useVariantUpdateMutation = makeMutation< VariantUpdate, VariantUpdateVariables >(variantUpdateMutation); @@ -425,7 +424,7 @@ export const variantCreateMutation = gql` } } `; -export const TypedVariantCreateMutation = TypedMutation< +export const useVariantCreateMutation = makeMutation< VariantCreate, VariantCreateVariables >(variantCreateMutation); @@ -446,7 +445,7 @@ export const productImageDeleteMutation = gql` } } `; -export const TypedProductImageDeleteMutation = TypedMutation< +export const useProductImageDeleteMutation = makeMutation< ProductImageDelete, ProductImageDeleteVariables >(productImageDeleteMutation); @@ -465,7 +464,7 @@ export const productImageUpdateMutation = gql` } } `; -export const TypedProductImageUpdateMutation = TypedMutation< +export const useProductImageUpdateMutation = makeMutation< ProductImageUpdate, ProductImageUpdateVariables >(productImageUpdateMutation); @@ -484,7 +483,7 @@ export const variantImageAssignMutation = gql` } } `; -export const TypedVariantImageAssignMutation = TypedMutation< +export const useVariantImageAssignMutation = makeMutation< VariantImageAssign, VariantImageAssignVariables >(variantImageAssignMutation); @@ -503,7 +502,7 @@ export const variantImageUnassignMutation = gql` } } `; -export const TypedVariantImageUnassignMutation = TypedMutation< +export const useVariantImageUnassignMutation = makeMutation< VariantImageUnassign, VariantImageUnassignVariables >(variantImageUnassignMutation); @@ -518,7 +517,7 @@ export const productBulkDeleteMutation = gql` } } `; -export const TypedProductBulkDeleteMutation = TypedMutation< +export const useProductBulkDeleteMutation = makeMutation< productBulkDelete, productBulkDeleteVariables >(productBulkDeleteMutation); @@ -533,7 +532,7 @@ export const productBulkPublishMutation = gql` } } `; -export const TypedProductBulkPublishMutation = TypedMutation< +export const useProductBulkPublishMutation = makeMutation< productBulkPublish, productBulkPublishVariables >(productBulkPublishMutation); @@ -566,7 +565,7 @@ export const ProductVariantBulkDeleteMutation = gql` } } `; -export const TypedProductVariantBulkDeleteMutation = TypedMutation< +export const useProductVariantBulkDeleteMutation = makeMutation< ProductVariantBulkDelete, ProductVariantBulkDeleteVariables >(ProductVariantBulkDeleteMutation); diff --git a/src/products/queries.ts b/src/products/queries.ts index 80d5d8a1f..e94ea7379 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -10,7 +10,6 @@ import { warehouseFragment } from "@saleor/fragments/warehouses"; import makeQuery from "@saleor/hooks/makeQuery"; import gql from "graphql-tag"; -import { TypedQuery } from "../queries"; import { CountAllProducts } from "./types/CountAllProducts"; import { CreateMultipleVariantsData, @@ -150,10 +149,9 @@ const productListQuery = gql` } } `; -export const TypedProductListQuery = TypedQuery< - ProductList, - ProductListVariables ->(productListQuery); +export const useProductListQuery = makeQuery( + productListQuery +); const countAllProductsQuery = gql` query CountAllProducts { @@ -174,7 +172,7 @@ const productDetailsQuery = gql` } } `; -export const TypedProductDetailsQuery = TypedQuery< +export const useProductDetails = makeQuery< ProductDetails, ProductDetailsVariables >(productDetailsQuery); @@ -187,7 +185,7 @@ const productVariantQuery = gql` } } `; -export const TypedProductVariantQuery = TypedQuery< +export const useProductVariantQuery = makeQuery< ProductVariantDetails, ProductVariantDetailsVariables >(productVariantQuery); @@ -231,7 +229,7 @@ const productVariantCreateQuery = gql` } } `; -export const TypedProductVariantCreateQuery = TypedQuery< +export const useProductVariantCreateQuery = makeQuery< ProductVariantCreateData, ProductVariantCreateDataVariables >(productVariantCreateQuery); @@ -253,7 +251,7 @@ const productImageQuery = gql` } } `; -export const TypedProductImageQuery = TypedQuery< +export const useProductImageQuery = makeQuery< ProductImageById, ProductImageByIdVariables >(productImageQuery); @@ -288,7 +286,7 @@ const availableInGridAttributes = gql` } } `; -export const AvailableInGridAttributesQuery = TypedQuery< +export const useAvailableInGridAttributesQuery = makeQuery< GridAttributes, GridAttributesVariables >(availableInGridAttributes); diff --git a/src/products/views/ProductCreate.tsx b/src/products/views/ProductCreate.tsx index 342596a34..18a45ab71 100644 --- a/src/products/views/ProductCreate.tsx +++ b/src/products/views/ProductCreate.tsx @@ -10,12 +10,11 @@ import { useWarehouseList } from "@saleor/warehouses/queries"; import React from "react"; import { useIntl } from "react-intl"; -import { decimal, maybe, weight } from "../../misc"; +import { decimal, weight } from "../../misc"; import ProductCreatePage, { ProductCreatePageSubmitData } from "../components/ProductCreatePage"; -import { TypedProductCreateMutation } from "../mutations"; -import { ProductCreate } from "../types/ProductCreate"; +import { useProductCreateMutation } from "../mutations"; import { productListUrl, productUrl } from "../urls"; export const ProductCreateView: React.FC = () => { @@ -53,118 +52,104 @@ export const ProductCreateView: React.FC = () => { const handleBack = () => navigate(productListUrl()); - const handleSuccess = (data: ProductCreate) => { - if (data.productCreate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage({ - defaultMessage: "Product created" - }) - }); - navigate(productUrl(data.productCreate.product.id)); + const [productCreate, productCreateOpts] = useProductCreateMutation({ + onCompleted: data => { + if (data.productCreate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage({ + defaultMessage: "Product created" + }) + }); + navigate(productUrl(data.productCreate.product.id)); + } } + }); + + const handleSubmit = (formData: ProductCreatePageSubmitData) => { + productCreate({ + variables: { + attributes: formData.attributes.map(attribute => ({ + id: attribute.id, + values: attribute.value + })), + basePrice: decimal(formData.basePrice), + category: formData.category, + chargeTaxes: formData.chargeTaxes, + collections: formData.collections, + descriptionJson: JSON.stringify(formData.description), + isPublished: formData.isPublished, + name: formData.name, + productType: formData.productType, + publicationDate: + formData.publicationDate !== "" ? formData.publicationDate : null, + seo: { + description: formData.seoDescription, + title: formData.seoTitle + }, + sku: formData.sku, + stocks: formData.stocks.map(stock => ({ + quantity: parseInt(stock.value, 0), + warehouse: stock.id + })), + trackInventory: formData.trackInventory, + weight: weight(formData.weight) + } + }); }; return ( - - {(productCreate, productCreateOpts) => { - const handleSubmit = (formData: ProductCreatePageSubmitData) => { - productCreate({ - variables: { - attributes: formData.attributes.map(attribute => ({ - id: attribute.id, - values: attribute.value - })), - basePrice: decimal(formData.basePrice), - category: formData.category, - chargeTaxes: formData.chargeTaxes, - collections: formData.collections, - descriptionJson: JSON.stringify(formData.description), - isPublished: formData.isPublished, - name: formData.name, - productType: formData.productType, - publicationDate: - formData.publicationDate !== "" - ? formData.publicationDate - : null, - seo: { - description: formData.seoDescription, - title: formData.seoTitle - }, - sku: formData.sku, - stocks: formData.stocks.map(stock => ({ - quantity: parseInt(stock.value, 0), - warehouse: stock.id - })), - trackInventory: formData.trackInventory, - weight: weight(formData.weight) - } - }); - }; - - return ( - <> - - shop.defaultCurrency)} - categories={maybe( - () => searchCategoryOpts.data.search.edges, - [] - ).map(edge => edge.node)} - collections={maybe( - () => searchCollectionOpts.data.search.edges, - [] - ).map(edge => edge.node)} - disabled={productCreateOpts.loading} - errors={productCreateOpts.data?.productCreate.errors || []} - fetchCategories={searchCategory} - fetchCollections={searchCollection} - fetchProductTypes={searchProductTypes} - header={intl.formatMessage({ - defaultMessage: "New Product", - description: "page header" - })} - productTypes={maybe(() => - searchProductTypesOpts.data.search.edges.map(edge => edge.node) - )} - onBack={handleBack} - onSubmit={handleSubmit} - saveButtonBarState={productCreateOpts.status} - fetchMoreCategories={{ - hasMore: maybe( - () => searchCategoryOpts.data.search.pageInfo.hasNextPage - ), - loading: searchCategoryOpts.loading, - onFetchMore: loadMoreCategories - }} - fetchMoreCollections={{ - hasMore: maybe( - () => searchCollectionOpts.data.search.pageInfo.hasNextPage - ), - loading: searchCollectionOpts.loading, - onFetchMore: loadMoreCollections - }} - fetchMoreProductTypes={{ - hasMore: maybe( - () => searchProductTypesOpts.data.search.pageInfo.hasNextPage - ), - loading: searchProductTypesOpts.loading, - onFetchMore: loadMoreProductTypes - }} - warehouses={ - warehouses.data?.warehouses.edges.map(edge => edge.node) || [] - } - weightUnit={shop?.defaultWeightUnit} - /> - - ); - }} - + <> + + edge.node + )} + collections={(searchCollectionOpts.data?.search.edges || []).map( + edge => edge.node + )} + disabled={productCreateOpts.loading} + errors={productCreateOpts.data?.productCreate.errors || []} + fetchCategories={searchCategory} + fetchCollections={searchCollection} + fetchProductTypes={searchProductTypes} + header={intl.formatMessage({ + defaultMessage: "New Product", + description: "page header" + })} + productTypes={searchProductTypesOpts.data?.search.edges.map( + edge => edge.node + )} + onBack={handleBack} + onSubmit={handleSubmit} + saveButtonBarState={productCreateOpts.status} + fetchMoreCategories={{ + hasMore: searchCategoryOpts.data?.search.pageInfo.hasNextPage, + loading: searchCategoryOpts.loading, + onFetchMore: loadMoreCategories + }} + fetchMoreCollections={{ + hasMore: searchCollectionOpts.data?.search.pageInfo.hasNextPage, + loading: searchCollectionOpts.loading, + onFetchMore: loadMoreCollections + }} + fetchMoreProductTypes={{ + hasMore: searchProductTypesOpts.data?.search.pageInfo.hasNextPage, + loading: searchProductTypesOpts.loading, + onFetchMore: loadMoreProductTypes + }} + warehouses={ + warehouses.data?.warehouses.edges.map(edge => edge.node) || [] + } + weightUnit={shop?.defaultWeightUnit} + /> + ); }; export default ProductCreateView; diff --git a/src/products/views/ProductImage.tsx b/src/products/views/ProductImage.tsx index 23186bdd8..2efe9cbd5 100644 --- a/src/products/views/ProductImage.tsx +++ b/src/products/views/ProductImage.tsx @@ -7,14 +7,12 @@ import { commonMessages } from "@saleor/intl"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { maybe } from "../../misc"; import ProductImagePage from "../components/ProductImagePage"; import { - TypedProductImageDeleteMutation, - TypedProductImageUpdateMutation + useProductImageDeleteMutation, + useProductImageUpdateMutation } from "../mutations"; -import { TypedProductImageQuery } from "../queries"; -import { ProductImageUpdate } from "../types/ProductImageUpdate"; +import { useProductImageQuery } from "../queries"; import { productImageUrl, ProductImageUrlQueryParams, @@ -38,93 +36,84 @@ export const ProductImage: React.FC = ({ const intl = useIntl(); const handleBack = () => navigate(productUrl(productId)); - const handleUpdateSuccess = (data: ProductImageUpdate) => { - if (data.productImageUpdate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); + + const { data, loading } = useProductImageQuery({ + displayLoader: true, + variables: { + imageId, + productId } + }); + + const [updateImage, updateResult] = useProductImageUpdateMutation({ + onCompleted: data => { + if (data.productImageUpdate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + } + } + }); + + const [deleteImage, deleteResult] = useProductImageDeleteMutation({ + onCompleted: handleBack + }); + + const product = data?.product; + + if (product === null) { + return navigate(productListUrl())} />; + } + + const handleDelete = () => deleteImage({ variables: { id: imageId } }); + const handleImageClick = (id: string) => () => + navigate(productImageUrl(productId, id)); + const handleUpdate = (formData: { description: string }) => { + updateImage({ + variables: { + alt: formData.description, + id: imageId + } + }); }; + const image = data?.product?.mainImage; + return ( - - {({ data, loading }) => { - const product = data?.product; - - if (product === null) { - return navigate(productListUrl())} />; + <> + + navigate( + productImageUrl(productId, imageId, { + action: "remove" + }) + ) } - - return ( - - {(updateImage, updateResult) => ( - - {(deleteImage, deleteResult) => { - const handleDelete = () => - deleteImage({ variables: { id: imageId } }); - const handleImageClick = (id: string) => () => - navigate(productImageUrl(productId, id)); - const handleUpdate = (formData: { description: string }) => { - updateImage({ - variables: { - alt: formData.description, - id: imageId - } - }); - }; - const image = data && data.product && data.product.mainImage; - - return ( - <> - data.product.name)} - image={image || null} - images={maybe(() => data.product.images)} - onBack={handleBack} - onDelete={() => - navigate( - productImageUrl(productId, imageId, { - action: "remove" - }) - ) - } - onRowClick={handleImageClick} - onSubmit={handleUpdate} - saveButtonBarState={updateResult.status} - /> - - navigate(productImageUrl(productId, imageId), true) - } - onConfirm={handleDelete} - open={params.action === "remove"} - title={intl.formatMessage({ - defaultMessage: "Delete Image", - description: "dialog header" - })} - variant="delete" - confirmButtonState={deleteResult.status} - > - - - - - - ); - }} - - )} - - ); - }} - + onRowClick={handleImageClick} + onSubmit={handleUpdate} + saveButtonBarState={updateResult.status} + /> + navigate(productImageUrl(productId, imageId), true)} + onConfirm={handleDelete} + open={params.action === "remove"} + title={intl.formatMessage({ + defaultMessage: "Delete Image", + description: "dialog header" + })} + variant="delete" + confirmButtonState={deleteResult.status} + > + + + + + ); }; export default ProductImage; diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 0f55ed6c0..e25ca42eb 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -45,18 +45,16 @@ import { FormattedMessage, useIntl } from "react-intl"; import ProductListPage from "../../components/ProductListPage"; import { - TypedProductBulkDeleteMutation, - TypedProductBulkPublishMutation, + useProductBulkDeleteMutation, + useProductBulkPublishMutation, useProductExport } from "../../mutations"; import { - AvailableInGridAttributesQuery, - TypedProductListQuery, + useAvailableInGridAttributesQuery, useCountAllProducts, - useInitialProductFilterDataQuery + useInitialProductFilterDataQuery, + useProductListQuery } from "../../queries"; -import { productBulkDelete } from "../../types/productBulkDelete"; -import { productBulkPublish } from "../../types/productBulkPublish"; import { productAddUrl, productListUrl, @@ -235,6 +233,53 @@ export const ProductList: React.FC = ({ params }) => { }), [params, settings.rowNumber] ); + const { data, loading, refetch } = useProductListQuery({ + displayLoader: true, + variables: queryVariables + }); + + function filterColumnIds(columns: ProductListColumns[]) { + return columns + .filter(isAttributeColumnValue) + .map(getAttributeIdFromColumnValue); + } + const attributes = useAvailableInGridAttributesQuery({ + variables: { first: 6, ids: filterColumnIds(settings.columns) } + }); + + const [ + productBulkDelete, + productBulkDeleteOpts + ] = useProductBulkDeleteMutation({ + onCompleted: data => { + if (data.productBulkDelete.errors.length === 0) { + closeModal(); + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + reset(); + refetch(); + } + } + }); + + const [ + productBulkPublish, + productBulkPublishOpts + ] = useProductBulkPublishMutation({ + onCompleted: data => { + if (data.productBulkPublish.errors.length === 0) { + closeModal(); + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + reset(); + refetch(); + } + } + }); const filterOpts = getFilterOpts( params, @@ -262,345 +307,259 @@ export const ProductList: React.FC = ({ params }) => { } ); - function filterColumnIds(columns: ProductListColumns[]) { - return columns - .filter(isAttributeColumnValue) - .map(getAttributeIdFromColumnValue); - } + const { loadNextPage, loadPreviousPage, pageInfo } = paginate( + maybe(() => data.products.pageInfo), + paginationState, + params + ); return ( - - {attributes => ( - - {({ data, loading, refetch }) => { - const { loadNextPage, loadPreviousPage, pageInfo } = paginate( - maybe(() => data.products.pageInfo), - paginationState, - params - ); - - const handleBulkDelete = (data: productBulkDelete) => { - if (data.productBulkDelete.errors.length === 0) { - closeModal(); - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - reset(); - refetch(); + <> + attributes.data.availableInGrid.edges.map(edge => edge.node), + [] + )} + currencySymbol={currencySymbol} + currentTab={currentTab} + defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]} + filterOpts={filterOpts} + gridAttributes={maybe( + () => attributes.data.grid.edges.map(edge => edge.node), + [] + )} + totalGridAttributes={maybe( + () => attributes.data.availableInGrid.totalCount, + 0 + )} + settings={settings} + loading={attributes.loading} + hasMore={maybe( + () => attributes.data.availableInGrid.pageInfo.hasNextPage, + false + )} + onAdd={() => navigate(productAddUrl)} + disabled={loading} + products={maybe(() => data.products.edges.map(edge => edge.node))} + onFetchMore={() => + attributes.loadMore( + (prev, next) => { + if ( + prev.availableInGrid.pageInfo.endCursor === + next.availableInGrid.pageInfo.endCursor + ) { + return prev; } - }; - - const handleBulkPublish = (data: productBulkPublish) => { - if (data.productBulkPublish.errors.length === 0) { - closeModal(); - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - reset(); - refetch(); + return { + ...prev, + availableInGrid: { + ...prev.availableInGrid, + edges: [ + ...prev.availableInGrid.edges, + ...next.availableInGrid.edges + ], + pageInfo: next.availableInGrid.pageInfo + } + }; + }, + { + after: attributes.data.availableInGrid.pageInfo.endCursor + } + ) + } + onNextPage={loadNextPage} + onPreviousPage={loadPreviousPage} + onUpdateListSettings={updateListSettings} + pageInfo={pageInfo} + onRowClick={id => () => navigate(productUrl(id))} + onAll={resetFilters} + toolbar={ + <> + - - - openModal("delete", { - ids: listElements - }) - } - > - - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - onSearchChange={handleSearchChange} - onFilterChange={changeFilters} - onTabSave={() => openModal("save-search")} - onTabDelete={() => openModal("delete-search")} - onTabChange={handleTabChange} - initialSearch={params.query || ""} - tabs={getFilterTabs().map(tab => tab.name)} - onExport={() => openModal("export")} - /> - - productBulkDelete({ - variables: { ids: params.ids } - }) - } - title={intl.formatMessage({ - defaultMessage: "Delete Products", - description: "dialog header" - })} - variant="delete" - > - - params.ids.length), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - - productBulkPublish({ - variables: { - ids: params.ids, - isPublished: true - } - }) - } - title={intl.formatMessage({ - defaultMessage: "Publish Products", - description: "dialog header" - })} - > - - params.ids.length), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - - productBulkPublish({ - variables: { - ids: params.ids, - isPublished: false - } - }) - } - title={intl.formatMessage({ - defaultMessage: "Unpublish Products", - description: "dialog header" - })} - > - - params.ids.length), - displayQuantity: ( - - {maybe(() => params.ids.length)} - - ) - }} - /> - - - edge.node)} - hasMore={ - searchAttributes.result.data?.search.pageInfo - .hasNextPage - } - loading={searchAttributes.result.loading} - onFetch={searchAttributes.search} - onFetchMore={searchAttributes.loadMore} - open={params.action === "export"} - confirmButtonState={exportProductsOpts.status} - errors={ - exportProductsOpts.data?.exportProducts.errors || [] - } - productQuantity={{ - all: countAllProducts.data?.products.totalCount, - filter: data?.products.totalCount - }} - selectedProducts={listElements.length} - warehouses={ - warehouses.data?.warehouses.edges.map( - edge => edge.node - ) || [] - } - onClose={closeModal} - onSubmit={data => - exportProducts({ - variables: { - input: { - ...data, - filter, - ids: listElements - } - } - }) - } - /> - - tabs[currentTab - 1].name, - "..." - )} - /> - - )} - - )} - - ); - }} - - )} - + > + + + + + openModal("delete", { + ids: listElements + }) + } + > + + + + } + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + onSearchChange={handleSearchChange} + onFilterChange={changeFilters} + onTabSave={() => openModal("save-search")} + onTabDelete={() => openModal("delete-search")} + onTabChange={handleTabChange} + initialSearch={params.query || ""} + tabs={getFilterTabs().map(tab => tab.name)} + onExport={() => openModal("export")} + /> + + productBulkDelete({ + variables: { ids: params.ids } + }) + } + title={intl.formatMessage({ + defaultMessage: "Delete Products", + description: "dialog header" + })} + variant="delete" + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + + + productBulkPublish({ + variables: { + ids: params.ids, + isPublished: true + } + }) + } + title={intl.formatMessage({ + defaultMessage: "Publish Products", + description: "dialog header" + })} + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + + + productBulkPublish({ + variables: { + ids: params.ids, + isPublished: false + } + }) + } + title={intl.formatMessage({ + defaultMessage: "Unpublish Products", + description: "dialog header" + })} + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + + edge.node + )} + hasMore={searchAttributes.result.data?.search.pageInfo.hasNextPage} + loading={searchAttributes.result.loading} + onFetch={searchAttributes.search} + onFetchMore={searchAttributes.loadMore} + open={params.action === "export"} + confirmButtonState={exportProductsOpts.status} + errors={exportProductsOpts.data?.exportProducts.errors || []} + productQuantity={{ + all: countAllProducts.data?.products.totalCount, + filter: data?.products.totalCount + }} + selectedProducts={listElements.length} + warehouses={ + warehouses.data?.warehouses.edges.map(edge => edge.node) || [] + } + onClose={closeModal} + onSubmit={data => + exportProducts({ + variables: { + input: { + ...data, + filter, + ids: listElements + } + } + }) + } + /> + + tabs[currentTab - 1].name, "...")} + /> + ); }; export default ProductList; diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index 77b4ea440..e25e4df6b 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -11,6 +11,15 @@ import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; +import { + useProductDeleteMutation, + useProductImageCreateMutation, + useProductImageDeleteMutation, + useProductImagesReorder, + useProductUpdateMutation, + useProductVariantBulkDeleteMutation, + useSimpleProductUpdateMutation +} from "@saleor/products/mutations"; import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; @@ -20,14 +29,9 @@ import { FormattedMessage, useIntl } from "react-intl"; import { getMutationState, maybe } from "../../../misc"; import ProductUpdatePage from "../../components/ProductUpdatePage"; -import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; -import { TypedProductDetailsQuery } from "../../queries"; -import { - ProductImageCreate, - ProductImageCreateVariables -} from "../../types/ProductImageCreate"; +import { useProductDetails } from "../../queries"; +import { ProductImageCreateVariables } from "../../types/ProductImageCreate"; import { ProductUpdate as ProductUpdateMutationResult } from "../../types/ProductUpdate"; -import { ProductVariantBulkDelete } from "../../types/ProductVariantBulkDelete"; import { productImageUrl, productListUrl, @@ -78,6 +82,86 @@ export const ProductUpdate: React.FC = ({ id, params }) => { }); const shop = useShop(); + const { data, loading, refetch } = useProductDetails({ + displayLoader: true, + variables: { + id + } + }); + + const handleUpdate = (data: ProductUpdateMutationResult) => { + if (data.productUpdate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + } + }; + const [updateProduct, updateProductOpts] = useProductUpdateMutation({ + onCompleted: handleUpdate + }); + const [ + updateSimpleProduct, + updateSimpleProductOpts + ] = useSimpleProductUpdateMutation({ + onCompleted: handleUpdate + }); + + const [ + reorderProductImages, + reorderProductImagesOpts + ] = useProductImagesReorder({}); + + const [deleteProduct, deleteProductOpts] = useProductDeleteMutation({ + onCompleted: () => { + notify({ + status: "success", + text: intl.formatMessage({ + defaultMessage: "Product removed" + }) + }); + navigate(productListUrl()); + } + }); + + const [ + createProductImage, + createProductImageOpts + ] = useProductImageCreateMutation({ + onCompleted: data => { + const imageError = data.productImageCreate.errors.find( + error => error.field === ("image" as keyof ProductImageCreateVariables) + ); + if (imageError) { + notify({ + status: "error", + text: intl.formatMessage(commonMessages.somethingWentWrong) + }); + } + } + }); + + const [deleteProductImage] = useProductImageDeleteMutation({ + onCompleted: () => + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }) + }); + + const [ + bulkProductVariantDelete, + bulkProductVariantDeleteOpts + ] = useProductVariantBulkDeleteMutation({ + onCompleted: data => { + if (data.productVariantBulkDelete.errors.length === 0) { + closeModal(); + reset(); + refetch(); + } + } + }); + const [openModal, closeModal] = createDialogActionHandlers< ProductUrlDialog, ProductUrlQueryParams @@ -85,256 +169,169 @@ export const ProductUpdate: React.FC = ({ id, params }) => { const handleBack = () => navigate(productListUrl()); + const product = data?.product; + + if (product === null) { + return ; + } + + const handleVariantAdd = () => navigate(productVariantAddUrl(id)); + + const handleImageDelete = (id: string) => () => + deleteProductImage({ variables: { id } }); + const handleImageEdit = (imageId: string) => () => + navigate(productImageUrl(id, imageId)); + const handleSubmit = createUpdateHandler( + product, + variables => updateProduct({ variables }), + variables => updateSimpleProduct({ variables }) + ); + const handleImageUpload = createImageUploadHandler(id, variables => + createProductImage({ variables }) + ); + const handleImageReorder = createImageReorderHandler(product, variables => + reorderProductImages({ variables }) + ); + + const disableFormSave = + createProductImageOpts.loading || + deleteProductOpts.loading || + reorderProductImagesOpts.loading || + updateProductOpts.loading || + loading; + const formTransitionState = getMutationState( + updateProductOpts.called || updateSimpleProductOpts.called, + updateProductOpts.loading || updateSimpleProductOpts.loading, + maybe(() => updateProductOpts.data.productUpdate.errors), + maybe(() => updateSimpleProductOpts.data.productUpdate.errors), + maybe(() => updateSimpleProductOpts.data.productVariantUpdate.errors) + ); + + const categories = maybe( + () => searchCategoriesOpts.data.search.edges, + [] + ).map(edge => edge.node); + const collections = maybe( + () => searchCollectionsOpts.data.search.edges, + [] + ).map(edge => edge.node); + const errors = [ + ...maybe(() => updateProductOpts.data.productUpdate.errors, []), + ...maybe(() => updateSimpleProductOpts.data.productUpdate.errors, []) + ]; + return ( - - {({ data, loading, refetch }) => { - const product = data?.product; - - if (product === null) { - return ; + <> + data.product.name)} /> + data.product.images)} + header={maybe(() => product.name)} + placeholderImage={placeholderImg} + product={product} + warehouses={ + warehouses.data?.warehouses.edges.map(edge => edge.node) || [] } - - const handleDelete = () => { - notify({ - status: "success", - text: intl.formatMessage({ - defaultMessage: "Product removed" - }) - }); - navigate(productListUrl()); - }; - const handleUpdate = (data: ProductUpdateMutationResult) => { - if (data.productUpdate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - } - }; - - const handleImageCreate = (data: ProductImageCreate) => { - const imageError = data.productImageCreate.errors.find( - error => - error.field === ("image" as keyof ProductImageCreateVariables) - ); - if (imageError) { - notify({ - status: "error", - text: intl.formatMessage(commonMessages.somethingWentWrong) - }); - } - }; - const handleImageDeleteSuccess = () => - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - const handleVariantAdd = () => navigate(productVariantAddUrl(id)); - - const handleBulkProductVariantDelete = ( - data: ProductVariantBulkDelete - ) => { - if (data.productVariantBulkDelete.errors.length === 0) { - closeModal(); - reset(); - refetch(); - } - }; - - return ( - product.variants)} + onBack={handleBack} + onDelete={() => openModal("remove")} + onImageReorder={handleImageReorder} + onSubmit={handleSubmit} + onVariantAdd={handleVariantAdd} + onVariantsAdd={() => navigate(productVariantCreatorUrl(id))} + onVariantShow={variantId => () => + navigate(productVariantEditUrl(product.id, variantId))} + onImageUpload={handleImageUpload} + onImageEdit={handleImageEdit} + onImageDelete={handleImageDelete} + toolbar={ + + openModal("remove-variants", { + ids: listElements + }) + } > - {({ - bulkProductVariantDelete, - createProductImage, - deleteProduct, - deleteProductImage, - reorderProductImages, - updateProduct, - updateSimpleProduct - }) => { - const handleImageDelete = (id: string) => () => - deleteProductImage.mutate({ id }); - const handleImageEdit = (imageId: string) => () => - navigate(productImageUrl(id, imageId)); - const handleSubmit = createUpdateHandler( - product, - updateProduct.mutate, - updateSimpleProduct.mutate - ); - const handleImageUpload = createImageUploadHandler( - id, - createProductImage.mutate - ); - const handleImageReorder = createImageReorderHandler( - product, - reorderProductImages.mutate - ); - - const disableFormSave = - createProductImage.opts.loading || - deleteProduct.opts.loading || - reorderProductImages.opts.loading || - updateProduct.opts.loading || - loading; - const formTransitionState = getMutationState( - updateProduct.opts.called || updateSimpleProduct.opts.called, - updateProduct.opts.loading || updateSimpleProduct.opts.loading, - maybe(() => updateProduct.opts.data.productUpdate.errors), - maybe(() => updateSimpleProduct.opts.data.productUpdate.errors), - maybe( - () => - updateSimpleProduct.opts.data.productVariantUpdate.errors - ) - ); - - const categories = maybe( - () => searchCategoriesOpts.data.search.edges, - [] - ).map(edge => edge.node); - const collections = maybe( - () => searchCollectionsOpts.data.search.edges, - [] - ).map(edge => edge.node); - const errors = [ - ...maybe( - () => updateProduct.opts.data.productUpdate.errors, - [] - ), - ...maybe( - () => updateSimpleProduct.opts.data.productUpdate.errors, - [] - ) - ]; - - return ( - <> - data.product.name)} /> - data.product.images)} - header={maybe(() => product.name)} - placeholderImage={placeholderImg} - product={product} - warehouses={ - warehouses.data?.warehouses.edges.map( - edge => edge.node - ) || [] - } - variants={maybe(() => product.variants)} - onBack={handleBack} - onDelete={() => openModal("remove")} - onImageReorder={handleImageReorder} - onSubmit={handleSubmit} - onVariantAdd={handleVariantAdd} - onVariantsAdd={() => navigate(productVariantCreatorUrl(id))} - onVariantShow={variantId => () => - navigate(productVariantEditUrl(product.id, variantId))} - onImageUpload={handleImageUpload} - onImageEdit={handleImageEdit} - onImageDelete={handleImageDelete} - toolbar={ - - openModal("remove-variants", { - ids: listElements - }) - } - > - - - } - isChecked={isSelected} - selected={listElements.length} - toggle={toggle} - toggleAll={toggleAll} - fetchMoreCategories={{ - hasMore: maybe( - () => - searchCategoriesOpts.data.search.pageInfo.hasNextPage - ), - loading: searchCategoriesOpts.loading, - onFetchMore: loadMoreCategories - }} - fetchMoreCollections={{ - hasMore: maybe( - () => - searchCollectionsOpts.data.search.pageInfo.hasNextPage - ), - loading: searchCollectionsOpts.loading, - onFetchMore: loadMoreCollections - }} - /> - deleteProduct.mutate({ id })} - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete Product", - description: "dialog header" - })} - > - - - - - - bulkProductVariantDelete.mutate({ - ids: params.ids - }) - } - variant="delete" - title={intl.formatMessage({ - defaultMessage: "Delete Product Variants", - description: "dialog header" - })} - > - - params.ids.length), - displayQuantity: ( - {maybe(() => params.ids.length)} - ) - }} - /> - - - - ); + + + } + isChecked={isSelected} + selected={listElements.length} + toggle={toggle} + toggleAll={toggleAll} + fetchMoreCategories={{ + hasMore: maybe( + () => searchCategoriesOpts.data.search.pageInfo.hasNextPage + ), + loading: searchCategoriesOpts.loading, + onFetchMore: loadMoreCategories + }} + fetchMoreCollections={{ + hasMore: maybe( + () => searchCollectionsOpts.data.search.pageInfo.hasNextPage + ), + loading: searchCollectionsOpts.loading, + onFetchMore: loadMoreCollections + }} + /> + deleteProduct({ variables: { id } })} + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete Product", + description: "dialog header" + })} + > + + - ); - }} - + /> + + + + bulkProductVariantDelete({ + variables: { + ids: params.ids + } + }) + } + variant="delete" + title={intl.formatMessage({ + defaultMessage: "Delete Product Variants", + description: "dialog header" + })} + > + + params.ids.length), + displayQuantity: {maybe(() => params.ids.length)} + }} + /> + + + ); }; export default ProductUpdate; diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index 15150049d..5b58d89bf 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -15,12 +15,14 @@ import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog import ProductVariantPage, { ProductVariantPageSubmitData } from "../components/ProductVariantPage"; -import ProductVariantOperations from "../containers/ProductVariantOperations"; -import { TypedProductVariantQuery } from "../queries"; import { - VariantUpdate, - VariantUpdate_productVariantUpdate_errors -} from "../types/VariantUpdate"; + useVariantDeleteMutation, + useVariantImageAssignMutation, + useVariantImageUnassignMutation, + useVariantUpdateMutation +} from "../mutations"; +import { useProductVariantQuery } from "../queries"; +import { VariantUpdate_productVariantUpdate_errors } from "../types/VariantUpdate"; import { productUrl, productVariantAddUrl, @@ -59,6 +61,13 @@ export const ProductVariant: React.FC = ({ } }); + const { data, loading } = useProductVariantQuery({ + displayLoader: true, + variables: { + id: variantId + } + }); + const [openModal] = createDialogActionHandlers< ProductVariantEditUrlDialog, ProductVariantEditUrlQueryParams @@ -70,132 +79,122 @@ export const ProductVariant: React.FC = ({ const handleBack = () => navigate(productUrl(productId)); - return ( - - {({ data, loading }) => { - const variant = data?.productVariant; + const [assignImage, assignImageOpts] = useVariantImageAssignMutation({}); + const [unassignImage, unassignImageOpts] = useVariantImageUnassignMutation( + {} + ); + const [deleteVariant, deleteVariantOpts] = useVariantDeleteMutation({ + onCompleted: () => { + notify({ + status: "success", + text: intl.formatMessage({ + defaultMessage: "Variant removed" + }) + }); + navigate(productUrl(productId)); + } + }); + const [updateVariant, updateVariantOpts] = useVariantUpdateMutation({ + onCompleted: data => { + if (data.productVariantUpdate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + } else { + setErrors(data.productVariantUpdate.errors); + } + } + }); - if (variant === null) { - return ; - } + const variant = data?.productVariant; - const handleDelete = () => { - notify({ - status: "success", - text: intl.formatMessage({ - defaultMessage: "Variant removed" - }) - }); - navigate(productUrl(productId)); - }; - const handleUpdate = (data: VariantUpdate) => { - if (data.productVariantUpdate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - } else { - setErrors(data.productVariantUpdate.errors); + if (variant === null) { + return ; + } + + const disableFormSave = + loading || + deleteVariantOpts.loading || + updateVariantOpts.loading || + assignImageOpts.loading || + unassignImageOpts.loading; + + const handleImageSelect = (id: string) => () => { + if (variant) { + if (variant?.images?.map(image => image.id).indexOf(id) !== -1) { + unassignImage({ + variables: { + imageId: id, + variantId: variant.id } - }; + }); + } else { + assignImage({ + variables: { + imageId: id, + variantId: variant.id + } + }); + } + } + }; - return ( - - {({ assignImage, deleteVariant, updateVariant, unassignImage }) => { - const disableFormSave = - loading || - deleteVariant.opts.loading || - updateVariant.opts.loading || - assignImage.opts.loading || - unassignImage.opts.loading; - - const handleImageSelect = (id: string) => () => { - if (variant) { - if ( - variant.images && - variant.images.map(image => image.id).indexOf(id) !== -1 - ) { - unassignImage.mutate({ - imageId: id, - variantId: variant.id - }); - } else { - assignImage.mutate({ - imageId: id, - variantId: variant.id - }); - } - } - }; - - return ( - <> - - edge.node - ) || [] - } - onAdd={() => navigate(productVariantAddUrl(productId))} - onBack={handleBack} - onDelete={() => openModal("remove")} - onImageSelect={handleImageSelect} - onSubmit={(data: ProductVariantPageSubmitData) => - updateVariant.mutate({ - addStocks: data.addStocks.map( - mapFormsetStockToStockInput - ), - attributes: data.attributes.map(attribute => ({ - id: attribute.id, - values: [attribute.value] - })), - costPrice: decimal(data.costPrice), - id: variantId, - price: decimal(data.price), - removeStocks: data.removeStocks, - sku: data.sku, - stocks: data.updateStocks.map( - mapFormsetStockToStockInput - ), - trackInventory: data.trackInventory, - weight: weight(data.weight) - }) - } - onVariantClick={variantId => { - navigate(productVariantEditUrl(productId, variantId)); - }} - /> - - navigate(productVariantEditUrl(productId, variantId)) - } - onConfirm={() => - deleteVariant.mutate({ - id: variantId - }) - } - open={params.action === "remove"} - name={data?.productVariant?.name} - /> - - ); - }} - - ); - }} - + return ( + <> + + edge.node) || [] + } + onAdd={() => navigate(productVariantAddUrl(productId))} + 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) + } + }) + } + onVariantClick={variantId => { + navigate(productVariantEditUrl(productId, variantId)); + }} + /> + navigate(productVariantEditUrl(productId, variantId))} + onConfirm={() => + deleteVariant({ + variables: { + id: variantId + } + }) + } + open={params.action === "remove"} + name={data?.productVariant?.name} + /> + ); }; export default ProductVariant; diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index 5e5cef09a..36b821eff 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -12,9 +12,8 @@ import { decimal, weight } from "../../misc"; import ProductVariantCreatePage, { ProductVariantCreatePageSubmitData } from "../components/ProductVariantCreatePage"; -import { TypedVariantCreateMutation } from "../mutations"; -import { TypedProductVariantCreateQuery } from "../queries"; -import { VariantCreate } from "../types/VariantCreate"; +import { useVariantCreateMutation } from "../mutations"; +import { useProductVariantCreateQuery } from "../queries"; import { productListUrl, productUrl, productVariantEditUrl } from "../urls"; interface ProductVariantCreateProps { @@ -35,102 +34,90 @@ export const ProductVariant: React.FC = ({ } }); - return ( - - {({ data, loading: productLoading }) => { - const product = data?.product; + const { data, loading: productLoading } = useProductVariantCreateQuery({ + displayLoader: true, + variables: { id: productId } + }); - if (product === null) { - return navigate(productListUrl())} />; - } - - const handleCreateSuccess = (data: VariantCreate) => { - if (data.productVariantCreate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges) - }); - navigate( - productVariantEditUrl( - productId, - data.productVariantCreate.productVariant.id - ) - ); - } - }; - - return ( - - {(variantCreate, variantCreateResult) => { - const handleBack = () => navigate(productUrl(productId)); - const handleSubmit = ( - formData: ProductVariantCreatePageSubmitData - ) => - variantCreate({ - variables: { - input: { - attributes: formData.attributes - .filter(attribute => attribute.value !== "") - .map(attribute => ({ - id: attribute.id, - values: [attribute.value] - })), - costPrice: decimal(formData.costPrice), - price: decimal(formData.price), - product: productId, - sku: formData.sku, - stocks: formData.stocks.map(stock => ({ - quantity: parseInt(stock.value, 0), - warehouse: stock.id - })), - trackInventory: true, - weight: weight(formData.weight) - } - } - }); - const handleVariantClick = (id: string) => - navigate(productVariantEditUrl(productId, id)); - - const disableForm = productLoading || variantCreateResult.loading; - - return ( - <> - - edge.node - ) || [] - } - weightUnit={shop?.defaultWeightUnit} - /> - - ); - }} - + const [variantCreate, variantCreateResult] = useVariantCreateMutation({ + onCompleted: data => { + if (data.productVariantCreate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges) + }); + navigate( + productVariantEditUrl( + productId, + data.productVariantCreate.productVariant.id + ) ); - }} - + } + } + }); + + const product = data?.product; + + if (product === null) { + return navigate(productListUrl())} />; + } + + const handleBack = () => navigate(productUrl(productId)); + const handleSubmit = (formData: ProductVariantCreatePageSubmitData) => + variantCreate({ + variables: { + input: { + attributes: formData.attributes + .filter(attribute => attribute.value !== "") + .map(attribute => ({ + id: attribute.id, + values: [attribute.value] + })), + costPrice: decimal(formData.costPrice), + price: decimal(formData.price), + product: productId, + sku: formData.sku, + stocks: formData.stocks.map(stock => ({ + quantity: parseInt(stock.value, 0), + warehouse: stock.id + })), + trackInventory: true, + weight: weight(formData.weight) + } + } + }); + const handleVariantClick = (id: string) => + navigate(productVariantEditUrl(productId, id)); + + const disableForm = productLoading || variantCreateResult.loading; + + return ( + <> + + edge.node) || [] + } + weightUnit={shop?.defaultWeightUnit} + /> + ); }; export default ProductVariant;