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 { TypedMutationInnerProps } from "../../mutations";
import { TypedProductImagesReorder } from "../mutations";
import { useProductImagesReorder } from "../mutations";
import {
ProductImageReorder,
ProductImageReorderVariables
@ -24,10 +24,12 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
productId,
productImages,
...mutationProps
}) => (
<TypedProductImagesReorder {...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<ProductImagesReorderProviderProps>
...opts,
optimisticResponse
});
}, mutationResult)
}
</TypedProductImagesReorder>
);
}, mutationResult)}
</>
);
};
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 gql from "graphql-tag";
import { TypedQuery } from "../queries";
import { CountAllProducts } from "./types/CountAllProducts";
import {
CreateMultipleVariantsData,
@ -186,7 +185,7 @@ const productVariantQuery = gql`
}
}
`;
export const TypedProductVariantQuery = TypedQuery<
export const useProductVariantQuery = makeQuery<
ProductVariantDetails,
ProductVariantDetailsVariables
>(productVariantQuery);

View file

@ -14,8 +14,7 @@ import { decimal, maybe, 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,111 @@ 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 (
<TypedProductCreateMutation onCompleted={handleSuccess}>
{(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 (
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Product",
description: "window title"
})}
/>
<ProductCreatePage
currency={maybe(() => 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}
/>
</>
);
}}
</TypedProductCreateMutation>
<>
<WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Product",
description: "window title"
})}
/>
<ProductCreatePage
currency={maybe(() => 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}
/>
</>
);
};
export default ProductCreateView;

View file

@ -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<ProductUpdateProps> = ({
}
});
const { data, loading } = useProductVariantQuery({
displayLoader: true,
variables: {
id: variantId
}
});
const [openModal] = createDialogActionHandlers<
ProductVariantEditUrlDialog,
ProductVariantEditUrlQueryParams
@ -70,132 +79,125 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
const handleBack = () => navigate(productUrl(productId));
return (
<TypedProductVariantQuery displayLoader variables={{ id: variantId }}>
{({ 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 <NotFoundPage onBack={handleBack} />;
}
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 <NotFoundPage onBack={handleBack} />;
}
const disableFormSave =
loading ||
deleteVariantOpts.loading ||
updateVariantOpts.loading ||
assignImageOpts.loading ||
unassignImageOpts.loading;
const handleImageSelect = (id: string) => () => {
if (variant) {
if (
variant.images &&
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 (
<ProductVariantOperations
onDelete={handleDelete}
onUpdate={handleUpdate}
>
{({ 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 (
<>
<WindowTitle title={data?.productVariant?.name} />
<ProductVariantPage
defaultWeightUnit={shop?.defaultWeightUnit}
errors={errors}
saveButtonBarState={updateVariant.opts.status}
loading={disableFormSave}
placeholderImage={placeholderImg}
variant={variant}
header={variant?.name || variant?.sku}
warehouses={
warehouses.data?.warehouses.edges.map(
edge => 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));
}}
/>
<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>
return (
<>
<WindowTitle title={data?.productVariant?.name} />
<ProductVariantPage
defaultWeightUnit={shop?.defaultWeightUnit}
errors={errors}
saveButtonBarState={updateVariantOpts.status}
loading={disableFormSave}
placeholderImage={placeholderImg}
variant={variant}
header={variant?.name || variant?.sku}
warehouses={
warehouses.data?.warehouses.edges.map(edge => 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));
}}
/>
<ProductVariantDeleteDialog
confirmButtonState={deleteVariantOpts.status}
onClose={() => navigate(productVariantEditUrl(productId, variantId))}
onConfirm={() =>
deleteVariant({
variables: {
id: variantId
}
})
}
open={params.action === "remove"}
name={data?.productVariant?.name}
/>
</>
);
};
export default ProductVariant;