Use query hooks in product variant view

This commit is contained in:
dominik-zeglen 2020-08-24 12:29:54 +02:00
parent 0fc4d1afb2
commit 419525f493
5 changed files with 243 additions and 325 deletions

View file

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { TypedMutationInnerProps } from "../../mutations"; import { TypedMutationInnerProps } from "../../mutations";
import { TypedProductImagesReorder } from "../mutations"; import { useProductImagesReorder } from "../mutations";
import { import {
ProductImageReorder, ProductImageReorder,
ProductImageReorderVariables ProductImageReorderVariables
@ -24,10 +24,12 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
productId, productId,
productImages, productImages,
...mutationProps ...mutationProps
}) => ( }) => {
<TypedProductImagesReorder {...mutationProps}> const [mutate, mutationResult] = useProductImagesReorder(mutationProps);
{(mutate, mutationResult) =>
children(opts => { return (
<>
{children(opts => {
const productImagesMap = productImages.reduce((prev, curr) => { const productImagesMap = productImages.reduce((prev, curr) => {
prev[curr.id] = curr; prev[curr.id] = curr;
return prev; return prev;
@ -52,9 +54,9 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
...opts, ...opts,
optimisticResponse optimisticResponse
}); });
}, mutationResult) }, mutationResult)}
} </>
</TypedProductImagesReorder> );
); };
export default ProductImagesReorderProvider; export default ProductImagesReorderProvider;

View file

@ -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<VariantDeleteOperationsProps> = ({
children,
onDelete,
onUpdate,
onImageAssign,
onImageUnassign
}) => (
<TypedVariantImageAssignMutation onCompleted={onImageAssign}>
{(...assignImage) => (
<TypedVariantImageUnassignMutation onCompleted={onImageUnassign}>
{(...unassignImage) => (
<TypedVariantUpdateMutation onCompleted={onUpdate}>
{(...updateVariant) => (
<TypedVariantDeleteMutation onCompleted={onDelete}>
{(...deleteVariant) =>
children({
assignImage: getMutationProviderData(...assignImage),
deleteVariant: getMutationProviderData(...deleteVariant),
unassignImage: getMutationProviderData(...unassignImage),
updateVariant: getMutationProviderData(...updateVariant)
})
}
</TypedVariantDeleteMutation>
)}
</TypedVariantUpdateMutation>
)}
</TypedVariantImageUnassignMutation>
)}
</TypedVariantImageAssignMutation>
);
export default VariantUpdateOperations;

View file

@ -10,7 +10,6 @@ import { warehouseFragment } from "@saleor/fragments/warehouses";
import makeQuery from "@saleor/hooks/makeQuery"; import makeQuery from "@saleor/hooks/makeQuery";
import gql from "graphql-tag"; import gql from "graphql-tag";
import { TypedQuery } from "../queries";
import { CountAllProducts } from "./types/CountAllProducts"; import { CountAllProducts } from "./types/CountAllProducts";
import { import {
CreateMultipleVariantsData, CreateMultipleVariantsData,
@ -186,7 +185,7 @@ const productVariantQuery = gql`
} }
} }
`; `;
export const TypedProductVariantQuery = TypedQuery< export const useProductVariantQuery = makeQuery<
ProductVariantDetails, ProductVariantDetails,
ProductVariantDetailsVariables ProductVariantDetailsVariables
>(productVariantQuery); >(productVariantQuery);

View file

@ -14,8 +14,7 @@ import { decimal, maybe, weight } from "../../misc";
import ProductCreatePage, { import ProductCreatePage, {
ProductCreatePageSubmitData ProductCreatePageSubmitData
} from "../components/ProductCreatePage"; } from "../components/ProductCreatePage";
import { TypedProductCreateMutation } from "../mutations"; import { useProductCreateMutation } from "../mutations";
import { ProductCreate } from "../types/ProductCreate";
import { productListUrl, productUrl } from "../urls"; import { productListUrl, productUrl } from "../urls";
export const ProductCreateView: React.FC = () => { export const ProductCreateView: React.FC = () => {
@ -53,118 +52,111 @@ export const ProductCreateView: React.FC = () => {
const handleBack = () => navigate(productListUrl()); const handleBack = () => navigate(productListUrl());
const handleSuccess = (data: ProductCreate) => { const [productCreate, productCreateOpts] = useProductCreateMutation({
if (data.productCreate.errors.length === 0) { onCompleted: data => {
notify({ if (data.productCreate.errors.length === 0) {
status: "success", notify({
text: intl.formatMessage({ status: "success",
defaultMessage: "Product created" text: intl.formatMessage({
}) defaultMessage: "Product created"
}); })
navigate(productUrl(data.productCreate.product.id)); });
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 ( return (
<TypedProductCreateMutation onCompleted={handleSuccess}> <>
{(productCreate, productCreateOpts) => { <WindowTitle
const handleSubmit = (formData: ProductCreatePageSubmitData) => { title={intl.formatMessage({
productCreate({ defaultMessage: "Create Product",
variables: { description: "window title"
attributes: formData.attributes.map(attribute => ({ })}
id: attribute.id, />
values: attribute.value <ProductCreatePage
})), currency={maybe(() => shop.defaultCurrency)}
basePrice: decimal(formData.basePrice), categories={maybe(() => searchCategoryOpts.data.search.edges, []).map(
category: formData.category, edge => edge.node
chargeTaxes: formData.chargeTaxes, )}
collections: formData.collections, collections={maybe(
descriptionJson: JSON.stringify(formData.description), () => searchCollectionOpts.data.search.edges,
isPublished: formData.isPublished, []
name: formData.name, ).map(edge => edge.node)}
productType: formData.productType, disabled={productCreateOpts.loading}
publicationDate: errors={productCreateOpts.data?.productCreate.errors || []}
formData.publicationDate !== "" fetchCategories={searchCategory}
? formData.publicationDate fetchCollections={searchCollection}
: null, fetchProductTypes={searchProductTypes}
seo: { header={intl.formatMessage({
description: formData.seoDescription, defaultMessage: "New Product",
title: formData.seoTitle description: "page header"
}, })}
sku: formData.sku, productTypes={maybe(() =>
stocks: formData.stocks.map(stock => ({ searchProductTypesOpts.data.search.edges.map(edge => edge.node)
quantity: parseInt(stock.value, 0), )}
warehouse: stock.id onBack={handleBack}
})), onSubmit={handleSubmit}
trackInventory: formData.trackInventory, saveButtonBarState={productCreateOpts.status}
weight: weight(formData.weight) fetchMoreCategories={{
} hasMore: maybe(
}); () => searchCategoryOpts.data.search.pageInfo.hasNextPage
}; ),
loading: searchCategoryOpts.loading,
return ( onFetchMore: loadMoreCategories
<> }}
<WindowTitle fetchMoreCollections={{
title={intl.formatMessage({ hasMore: maybe(
defaultMessage: "Create Product", () => searchCollectionOpts.data.search.pageInfo.hasNextPage
description: "window title" ),
})} loading: searchCollectionOpts.loading,
/> onFetchMore: loadMoreCollections
<ProductCreatePage }}
currency={maybe(() => shop.defaultCurrency)} fetchMoreProductTypes={{
categories={maybe( hasMore: maybe(
() => searchCategoryOpts.data.search.edges, () => searchProductTypesOpts.data.search.pageInfo.hasNextPage
[] ),
).map(edge => edge.node)} loading: searchProductTypesOpts.loading,
collections={maybe( onFetchMore: loadMoreProductTypes
() => searchCollectionOpts.data.search.edges, }}
[] warehouses={
).map(edge => edge.node)} warehouses.data?.warehouses.edges.map(edge => edge.node) || []
disabled={productCreateOpts.loading} }
errors={productCreateOpts.data?.productCreate.errors || []} weightUnit={shop?.defaultWeightUnit}
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}
/>
</>
);
}}
</TypedProductCreateMutation>
); );
}; };
export default ProductCreateView; export default ProductCreateView;

View file

@ -15,12 +15,14 @@ import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog
import ProductVariantPage, { import ProductVariantPage, {
ProductVariantPageSubmitData ProductVariantPageSubmitData
} from "../components/ProductVariantPage"; } from "../components/ProductVariantPage";
import ProductVariantOperations from "../containers/ProductVariantOperations";
import { TypedProductVariantQuery } from "../queries";
import { import {
VariantUpdate, useVariantDeleteMutation,
VariantUpdate_productVariantUpdate_errors useVariantImageAssignMutation,
} from "../types/VariantUpdate"; useVariantImageUnassignMutation,
useVariantUpdateMutation
} from "../mutations";
import { useProductVariantQuery } from "../queries";
import { VariantUpdate_productVariantUpdate_errors } from "../types/VariantUpdate";
import { import {
productUrl, productUrl,
productVariantAddUrl, productVariantAddUrl,
@ -59,6 +61,13 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
} }
}); });
const { data, loading } = useProductVariantQuery({
displayLoader: true,
variables: {
id: variantId
}
});
const [openModal] = createDialogActionHandlers< const [openModal] = createDialogActionHandlers<
ProductVariantEditUrlDialog, ProductVariantEditUrlDialog,
ProductVariantEditUrlQueryParams ProductVariantEditUrlQueryParams
@ -70,132 +79,125 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
const handleBack = () => navigate(productUrl(productId)); const handleBack = () => navigate(productUrl(productId));
return ( const [assignImage, assignImageOpts] = useVariantImageAssignMutation({});
<TypedProductVariantQuery displayLoader variables={{ id: variantId }}> const [unassignImage, unassignImageOpts] = useVariantImageUnassignMutation(
{({ data, loading }) => { {}
const variant = data?.productVariant; );
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) { const variant = data?.productVariant;
return <NotFoundPage onBack={handleBack} />;
}
const handleDelete = () => { if (variant === null) {
notify({ return <NotFoundPage onBack={handleBack} />;
status: "success", }
text: intl.formatMessage({
defaultMessage: "Variant removed" const disableFormSave =
}) loading ||
}); deleteVariantOpts.loading ||
navigate(productUrl(productId)); updateVariantOpts.loading ||
}; assignImageOpts.loading ||
const handleUpdate = (data: VariantUpdate) => { unassignImageOpts.loading;
if (data.productVariantUpdate.errors.length === 0) {
notify({ const handleImageSelect = (id: string) => () => {
status: "success", if (variant) {
text: intl.formatMessage(commonMessages.savedChanges) if (
}); variant.images &&
} else { variant.images.map(image => image.id).indexOf(id) !== -1
setErrors(data.productVariantUpdate.errors); ) {
unassignImage({
variables: {
imageId: id,
variantId: variant.id
} }
}; });
} else {
assignImage({
variables: {
imageId: id,
variantId: variant.id
}
});
}
}
};
return ( return (
<ProductVariantOperations <>
onDelete={handleDelete} <WindowTitle title={data?.productVariant?.name} />
onUpdate={handleUpdate} <ProductVariantPage
> defaultWeightUnit={shop?.defaultWeightUnit}
{({ assignImage, deleteVariant, updateVariant, unassignImage }) => { errors={errors}
const disableFormSave = saveButtonBarState={updateVariantOpts.status}
loading || loading={disableFormSave}
deleteVariant.opts.loading || placeholderImage={placeholderImg}
updateVariant.opts.loading || variant={variant}
assignImage.opts.loading || header={variant?.name || variant?.sku}
unassignImage.opts.loading; warehouses={
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
const handleImageSelect = (id: string) => () => { }
if (variant) { onAdd={() => navigate(productVariantAddUrl(productId))}
if ( onBack={handleBack}
variant.images && onDelete={() => openModal("remove")}
variant.images.map(image => image.id).indexOf(id) !== -1 onImageSelect={handleImageSelect}
) { onSubmit={(data: ProductVariantPageSubmitData) =>
unassignImage.mutate({ updateVariant({
imageId: id, variables: {
variantId: variant.id addStocks: data.addStocks.map(mapFormsetStockToStockInput),
}); attributes: data.attributes.map(attribute => ({
} else { id: attribute.id,
assignImage.mutate({ values: [attribute.value]
imageId: id, })),
variantId: variant.id costPrice: decimal(data.costPrice),
}); id: variantId,
} price: decimal(data.price),
} removeStocks: data.removeStocks,
}; sku: data.sku,
stocks: data.updateStocks.map(mapFormsetStockToStockInput),
return ( trackInventory: data.trackInventory,
<> weight: weight(data.weight)
<WindowTitle title={data?.productVariant?.name} /> }
<ProductVariantPage })
defaultWeightUnit={shop?.defaultWeightUnit} }
errors={errors} onVariantClick={variantId => {
saveButtonBarState={updateVariant.opts.status} navigate(productVariantEditUrl(productId, variantId));
loading={disableFormSave} }}
placeholderImage={placeholderImg} />
variant={variant} <ProductVariantDeleteDialog
header={variant?.name || variant?.sku} confirmButtonState={deleteVariantOpts.status}
warehouses={ onClose={() => navigate(productVariantEditUrl(productId, variantId))}
warehouses.data?.warehouses.edges.map( onConfirm={() =>
edge => edge.node deleteVariant({
) || [] variables: {
} id: variantId
onAdd={() => navigate(productVariantAddUrl(productId))} }
onBack={handleBack} })
onDelete={() => openModal("remove")} }
onImageSelect={handleImageSelect} open={params.action === "remove"}
onSubmit={(data: ProductVariantPageSubmitData) => name={data?.productVariant?.name}
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));
}}
/>
<ProductVariantDeleteDialog
confirmButtonState={deleteVariant.opts.status}
onClose={() =>
navigate(productVariantEditUrl(productId, variantId))
}
onConfirm={() =>
deleteVariant.mutate({
id: variantId
})
}
open={params.action === "remove"}
name={data?.productVariant?.name}
/>
</>
);
}}
</ProductVariantOperations>
);
}}
</TypedProductVariantQuery>
); );
}; };
export default ProductVariant; export default ProductVariant;