Improve saving variant images (#2640)

* Improve saving variant images

* Update storybook types

* Add getSelectedMedia function for products

* Add test for handleAssignMedia in products

* Follow AAA test convention

* Add confirm button to media dialog selector

* Move dialog styles to separate file
This commit is contained in:
Dawid 2022-12-06 12:03:41 +01:00 committed by GitHub
parent b98d5d57ef
commit 0741d3ca2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 451 additions and 95 deletions

View file

@ -5,65 +5,58 @@ import {
DialogTitle, DialogTitle,
} from "@material-ui/core"; } from "@material-ui/core";
import BackButton from "@saleor/components/BackButton"; import BackButton from "@saleor/components/BackButton";
import ConfirmButton from "@saleor/components/ConfirmButton";
import { ProductMediaFragment } from "@saleor/graphql"; import { ProductMediaFragment } from "@saleor/graphql";
import { makeStyles } from "@saleor/macaw-ui"; import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import { buttonMessages } from "@saleor/intl";
import clsx from "clsx"; import clsx from "clsx";
import React from "react"; import React, { useState } from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
const useStyles = makeStyles( import { useStyles } from "./styles";
theme => ({
image: {
height: "100%",
objectFit: "contain",
userSelect: "none",
width: "100%",
},
imageContainer: {
background: "transparent",
border: "1px solid #eaeaea",
borderRadius: theme.spacing(),
cursor: "pointer",
height: theme.spacing(21.5),
overflow: "hidden",
padding: theme.spacing(2),
position: "relative",
transitionDuration: theme.transitions.duration.standard + "ms",
},
content: {
overflowY: "scroll",
},
root: {
display: "grid",
gridColumnGap: theme.spacing(2),
gridRowGap: theme.spacing(2),
gridTemplateColumns: "repeat(3, 1fr)",
maxWidth: "100%",
width: theme.breakpoints.values.lg,
[theme.breakpoints.down("sm")]: {
gridTemplateColumns: "repeat(2, 1fr)",
},
},
selectedImageContainer: {
borderColor: theme.palette.primary.main,
borderWidth: "2px",
},
}),
{ name: "ProductVariantImageSelectDialog" },
);
interface ProductVariantImageSelectDialogProps { interface ProductVariantImageSelectDialogProps {
media?: ProductMediaFragment[]; media?: ProductMediaFragment[];
selectedMedia?: string[]; selectedMedia?: string[];
open: boolean; open: boolean;
onClose(); onClose: () => void;
onMediaSelect(id: string); onConfirm: (selectedIds: string[]) => void;
} }
const ProductVariantMediaSelectDialog: React.FC<ProductVariantImageSelectDialogProps> = props => { const ProductVariantMediaSelectDialog: React.FC<ProductVariantImageSelectDialogProps> = props => {
const { media, open, selectedMedia, onClose, onMediaSelect } = props; const {
media,
open,
selectedMedia: initialMedia,
onClose,
onConfirm,
} = props;
const classes = useStyles(props); const classes = useStyles(props);
const [selectedMedia, setSelectedMedia] = useState(initialMedia);
useModalDialogOpen(open, {
onOpen: () => setSelectedMedia(initialMedia),
onClose: () => setSelectedMedia(initialMedia),
});
const handleMediaSelect = (id: string) => {
const isMediaAssigned = selectedMedia.includes(id);
if (isMediaAssigned) {
setSelectedMedia(selectedMedia =>
selectedMedia.filter(mediaId => mediaId !== id),
);
} else {
setSelectedMedia(selectedMedia => [...selectedMedia, id]);
}
};
const handleConfirm = () => {
onConfirm(selectedMedia);
onClose();
};
return ( return (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<DialogTitle> <DialogTitle>
@ -90,7 +83,7 @@ const ProductVariantMediaSelectDialog: React.FC<ProductVariantImageSelectDialogP
selectedMedia.indexOf(mediaObj.id) !== -1, selectedMedia.indexOf(mediaObj.id) !== -1,
}, },
])} ])}
onClick={onMediaSelect(mediaObj.id)} onClick={() => handleMediaSelect(mediaObj.id)}
key={mediaObj.id} key={mediaObj.id}
> >
<img <img
@ -105,6 +98,13 @@ const ProductVariantMediaSelectDialog: React.FC<ProductVariantImageSelectDialogP
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<BackButton onClick={onClose} /> <BackButton onClick={onClose} />
<ConfirmButton
transitionState="default"
onClick={handleConfirm}
data-test-id="submit"
>
<FormattedMessage {...buttonMessages.confirm} />
</ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View file

@ -0,0 +1,42 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
theme => ({
image: {
height: "100%",
objectFit: "contain",
userSelect: "none",
width: "100%",
},
imageContainer: {
background: "transparent",
border: "1px solid #eaeaea",
borderRadius: theme.spacing(),
cursor: "pointer",
height: theme.spacing(21.5),
overflow: "hidden",
padding: theme.spacing(2),
position: "relative",
transitionDuration: theme.transitions.duration.standard + "ms",
},
content: {
overflowY: "scroll",
},
root: {
display: "grid",
gridColumnGap: theme.spacing(2),
gridRowGap: theme.spacing(2),
gridTemplateColumns: "repeat(3, 1fr)",
maxWidth: "100%",
width: theme.breakpoints.values.lg,
[theme.breakpoints.down("sm")]: {
gridTemplateColumns: "repeat(2, 1fr)",
},
},
selectedImageContainer: {
borderColor: theme.palette.primary.main,
borderWidth: "2px",
},
}),
{ name: "ProductVariantImageSelectDialog" },
);

View file

@ -29,6 +29,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui"; import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
import { VariantDetailsChannelsAvailabilityCard } from "@saleor/products/components/ProductVariantChannels/ChannelsAvailabilityCard"; import { VariantDetailsChannelsAvailabilityCard } from "@saleor/products/components/ProductVariantChannels/ChannelsAvailabilityCard";
import { productUrl } from "@saleor/products/urls"; import { productUrl } from "@saleor/products/urls";
import { getSelectedMedia } from "@saleor/products/utils/data";
import { FetchMoreProps, RelayToFlat, ReorderAction } from "@saleor/types"; import { FetchMoreProps, RelayToFlat, ReorderAction } from "@saleor/types";
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { defineMessages, useIntl } from "react-intl";
@ -118,7 +119,6 @@ interface ProductVariantPageProps {
onAttributeSelectBlur: () => void; onAttributeSelectBlur: () => void;
onDelete(); onDelete();
onSubmit(data: ProductVariantUpdateSubmitData); onSubmit(data: ProductVariantUpdateSubmitData);
onMediaSelect(id: string);
onSetDefaultVariant(); onSetDefaultVariant();
onWarehouseConfigure(); onWarehouseConfigure();
} }
@ -140,7 +140,6 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
referenceProducts = [], referenceProducts = [],
attributeValues, attributeValues,
onDelete, onDelete,
onMediaSelect,
onSubmit, onSubmit,
onVariantPreorderDeactivate, onVariantPreorderDeactivate,
variantDeactivatePreoderButtonState, variantDeactivatePreoderButtonState,
@ -172,13 +171,9 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
setIsEndPreorderModalOpened, setIsEndPreorderModalOpened,
] = React.useState(false); ] = React.useState(false);
const variantMedia = variant?.media?.map(image => image.id);
const productMedia = [ const productMedia = [
...(variant?.product?.media ?? []), ...(variant?.product?.media ?? []),
]?.sort((prev, next) => (prev.sortOrder > next.sortOrder ? 1 : -1)); ]?.sort((prev, next) => (prev.sortOrder > next.sortOrder ? 1 : -1));
const media = productMedia
?.filter(image => variantMedia.indexOf(image.id) !== -1)
.sort((prev, next) => (prev.sortOrder > next.sortOrder ? 1 : -1));
const canOpenAssignReferencesAttributeDialog = !!assignReferencesAttributeId; const canOpenAssignReferencesAttributeDialog = !!assignReferencesAttributeId;
@ -246,6 +241,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
const selectionAttributes = data.attributes.filter( const selectionAttributes = data.attributes.filter(
byAttributeScope(VariantAttributeScope.VARIANT_SELECTION), byAttributeScope(VariantAttributeScope.VARIANT_SELECTION),
); );
const media = getSelectedMedia(productMedia, data.media);
const errors = [...apiErrors, ...validationErrors]; const errors = [...apiErrors, ...validationErrors];
@ -423,28 +419,28 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
/> />
)} )}
{variant && ( {variant && (
<VariantChannelsDialog <>
channelListings={variant.product.channelListings} <VariantChannelsDialog
selectedChannelListings={data.channelListings} channelListings={variant.product.channelListings}
open={isManageChannelsModalOpen} selectedChannelListings={data.channelListings}
onClose={toggleManageChannels} open={isManageChannelsModalOpen}
onConfirm={handlers.updateChannels} onClose={toggleManageChannels}
/> onConfirm={handlers.updateChannels}
/>
<ProductVariantMediaSelectDialog
onClose={toggleModal}
onConfirm={handlers.changeMedia}
open={isModalOpened}
media={productMedia}
selectedMedia={data.media}
/>
</>
)} )}
</> </>
); );
}} }}
</ProductVariantUpdateForm> </ProductVariantUpdateForm>
</Container> </Container>
{variant && (
<ProductVariantMediaSelectDialog
onClose={toggleModal}
onMediaSelect={onMediaSelect}
open={isModalOpened}
media={productMedia}
selectedMedia={variant?.media.map(image => image.id)}
/>
)}
{!!variant?.preorder && ( {!!variant?.preorder && (
<ProductVariantEndPreorderDialog <ProductVariantEndPreorderDialog
confirmButtonState={variantDeactivatePreoderButtonState} confirmButtonState={variantDeactivatePreoderButtonState}

View file

@ -44,6 +44,7 @@ import {
getStockInputFromVariant, getStockInputFromVariant,
} from "@saleor/products/utils/data"; } from "@saleor/products/utils/data";
import { import {
createMediaChangeHandler,
createPreorderEndDateChangeHandler, createPreorderEndDateChangeHandler,
getChannelsInput, getChannelsInput,
} from "@saleor/products/utils/handlers"; } from "@saleor/products/utils/handlers";
@ -78,6 +79,7 @@ export interface ProductVariantUpdateFormData extends MetadataFormData {
hasPreorderEndDate: boolean; hasPreorderEndDate: boolean;
preorderEndDateTime?: string; preorderEndDateTime?: string;
name: string; name: string;
media: string[];
} }
export interface ProductVariantUpdateData extends ProductVariantUpdateFormData { export interface ProductVariantUpdateData extends ProductVariantUpdateFormData {
channelListings: FormsetData< channelListings: FormsetData<
@ -126,6 +128,7 @@ export interface ProductVariantUpdateHandlers
Record<"addStock" | "deleteStock", (id: string) => void> { Record<"addStock" | "deleteStock", (id: string) => void> {
changePreorderEndDate: FormChange; changePreorderEndDate: FormChange;
changeMetadata: FormChange; changeMetadata: FormChange;
changeMedia: (ids: string[]) => void;
updateChannels: (selectedChannelsIds: string[]) => void; updateChannels: (selectedChannelsIds: string[]) => void;
fetchReferences: (value: string) => void; fetchReferences: (value: string) => void;
fetchMoreReferences: FetchMoreProps; fetchMoreReferences: FetchMoreProps;
@ -191,6 +194,7 @@ function useProductVariantUpdateForm(
weight: variant?.weight?.value.toString() || "", weight: variant?.weight?.value.toString() || "",
quantityLimitPerCustomer: variant?.quantityLimitPerCustomer || null, quantityLimitPerCustomer: variant?.quantityLimitPerCustomer || null,
name: variant?.name ?? "", name: variant?.name ?? "",
media: variant?.media?.map(({ id }) => id) || [],
}; };
const form = useForm(initial, undefined, { const form = useForm(initial, undefined, {
@ -300,6 +304,8 @@ function useProductVariantUpdateForm(
intl.formatMessage(errorMessages.preorderEndDateInFutureErrorText), intl.formatMessage(errorMessages.preorderEndDateInFutureErrorText),
); );
const handleMediaChange = createMediaChangeHandler(form, triggerChange);
const handleUpdateChannels = (selectedIds: string[]) => { const handleUpdateChannels = (selectedIds: string[]) => {
const allChannels = variant.product.channelListings.map(listing => { const allChannels = variant.product.channelListings.map(listing => {
const variantChannel = variant?.channelListings?.find( const variantChannel = variant?.channelListings?.find(
@ -428,6 +434,7 @@ function useProductVariantUpdateForm(
changeMetadata, changeMetadata,
changeStock: handleStockChange, changeStock: handleStockChange,
changePreorderEndDate: handlePreorderEndDateChange, changePreorderEndDate: handlePreorderEndDateChange,
changeMedia: handleMediaChange,
deleteStock: handleStockDelete, deleteStock: handleStockDelete,
fetchMoreReferences: handleFetchMoreReferences, fetchMoreReferences: handleFetchMoreReferences,
fetchReferences: handleFetchReferences, fetchReferences: handleFetchReferences,

View file

@ -0,0 +1,78 @@
import { getSelectedMedia } from "./data";
type GetSelectedMediaParams = Parameters<typeof getSelectedMedia>;
describe("Product media utils", () => {
it("should return selected media in proper order when media ids passed", () => {
// Arrange
const media: GetSelectedMediaParams[0] = [
{
id: "1",
sortOrder: 2,
},
{
id: "2",
sortOrder: 3,
},
{
id: "3",
sortOrder: 4,
},
{
id: "4",
sortOrder: 1,
},
];
const selectedIds: GetSelectedMediaParams[1] = ["1", "3", "4"];
// Act
const result = getSelectedMedia(media, selectedIds);
// Assert
const expectedResult = [
{
id: "4",
sortOrder: 1,
},
{
id: "1",
sortOrder: 2,
},
{
id: "3",
sortOrder: 4,
},
];
expect(result).toEqual(expectedResult);
});
it("should return empty array of media when no media ids passed", () => {
// Arrange
const media: GetSelectedMediaParams[0] = [
{
id: "1",
sortOrder: 2,
},
{
id: "2",
sortOrder: 3,
},
{
id: "3",
sortOrder: 4,
},
{
id: "4",
sortOrder: 1,
},
];
const selectedIds: GetSelectedMediaParams[1] = [];
// Act
const result = getSelectedMedia(media, selectedIds);
// Assert
expect(result).toEqual([]);
});
});

View file

@ -11,6 +11,7 @@ import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompl
import { import {
ProductDetailsVariantFragment, ProductDetailsVariantFragment,
ProductFragment, ProductFragment,
ProductMediaFragment,
ProductTypeQuery, ProductTypeQuery,
ProductVariantCreateDataQuery, ProductVariantCreateDataQuery,
ProductVariantFragment, ProductVariantFragment,
@ -247,3 +248,13 @@ export const getPreorderEndDateFormData = (endDate?: string) =>
export const getPreorderEndHourFormData = (endDate?: string) => export const getPreorderEndHourFormData = (endDate?: string) =>
endDate ? moment(endDate).format("HH:mm") : ""; endDate ? moment(endDate).format("HH:mm") : "";
export const getSelectedMedia = <
T extends Pick<ProductMediaFragment, "id" | "sortOrder">
>(
media: T[] = [],
selectedMediaIds: string[],
) =>
media
.filter(image => selectedMediaIds.indexOf(image.id) !== -1)
.sort((prev, next) => (prev.sortOrder > next.sortOrder ? 1 : -1));

View file

@ -0,0 +1,163 @@
import { ProductMediaType } from "@saleor/graphql";
import { handleAssignMedia } from "./handlers";
type HandleAssignMediaParams = Parameters<typeof handleAssignMedia>;
describe("Product handlers", () => {
it("should not alter product variant media when the same selected media ids as previously passed", async () => {
// Arrange
const media: HandleAssignMediaParams[0] = ["1", "2"];
const variant: HandleAssignMediaParams[1] = {
id: "1",
media: [
{
id: "1",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
{
id: "2",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
],
};
const assignMedia = jest.fn(() => Promise.resolve({}));
const unassignMedia = jest.fn(() => Promise.resolve({}));
// Act
await handleAssignMedia(media, variant, assignMedia, unassignMedia);
// Assert
expect(assignMedia).not.toHaveBeenCalled();
expect(unassignMedia).not.toHaveBeenCalled();
});
it("should assign media to product variant when more then all previous selected media ids passed", async () => {
// Arrange
const media: HandleAssignMediaParams[0] = ["1", "2", "3"];
const variant: HandleAssignMediaParams[1] = {
id: "1",
media: [
{
id: "3",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
],
};
const assignMedia = jest.fn(() => Promise.resolve({}));
const unassignMedia = jest.fn(() => Promise.resolve({}));
// Act
await handleAssignMedia(media, variant, assignMedia, unassignMedia);
// Assert
expect(assignMedia).toHaveBeenCalledTimes(2);
expect(assignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "1",
});
expect(assignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "2",
});
expect(unassignMedia).not.toHaveBeenCalled();
});
it("should unassign media from product variant when not all previous selected media ids passed", async () => {
// Arrange
const media: HandleAssignMediaParams[0] = ["3"];
const variant: HandleAssignMediaParams[1] = {
id: "1",
media: [
{
id: "1",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
{
id: "2",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
{
id: "3",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
],
};
const assignMedia = jest.fn(() => Promise.resolve({}));
const unassignMedia = jest.fn(() => Promise.resolve({}));
// Act
await handleAssignMedia(media, variant, assignMedia, unassignMedia);
// Assert
expect(assignMedia).not.toHaveBeenCalled();
expect(unassignMedia).toHaveBeenCalledTimes(2);
expect(unassignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "1",
});
expect(unassignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "2",
});
});
it("should assign and unassign media from product variant when not all but more selected media ids from previously selected passed", async () => {
// Arrange
const media: HandleAssignMediaParams[0] = ["1", "3"];
const variant: HandleAssignMediaParams[1] = {
id: "1",
media: [
{
id: "1",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
{
id: "2",
url: "",
type: ProductMediaType.IMAGE,
oembedData: null,
__typename: "ProductMedia",
},
],
};
const assignMedia = jest.fn(() => Promise.resolve({}));
const unassignMedia = jest.fn(() => Promise.resolve({}));
// Act
await handleAssignMedia(media, variant, assignMedia, unassignMedia);
// Assert
expect(assignMedia).toHaveBeenCalledTimes(1);
expect(assignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "3",
});
expect(unassignMedia).toHaveBeenCalledTimes(1);
expect(unassignMedia).toHaveBeenCalledWith({
variantId: "1",
mediaId: "2",
});
});
});

View file

@ -1,11 +1,20 @@
import { FetchResult } from "@apollo/client";
import { import {
ChannelData, ChannelData,
ChannelPriceAndPreorderData, ChannelPriceAndPreorderData,
ChannelPriceArgs, ChannelPriceArgs,
ChannelPriceData, ChannelPriceData,
} from "@saleor/channels/utils"; } from "@saleor/channels/utils";
import { ProductChannelListingAddInput } from "@saleor/graphql"; import {
ProductChannelListingAddInput,
ProductVariantFragment,
VariantMediaAssignMutation,
VariantMediaAssignMutationVariables,
VariantMediaUnassignMutation,
VariantMediaUnassignMutationVariables,
} from "@saleor/graphql";
import { FormChange, UseFormResult } from "@saleor/hooks/useForm"; import { FormChange, UseFormResult } from "@saleor/hooks/useForm";
import { diff } from "fast-array-diff";
import moment from "moment"; import moment from "moment";
export function createChannelsPriceChangeHandler( export function createChannelsPriceChangeHandler(
@ -144,3 +153,69 @@ export const createPreorderEndDateChangeHandler = (
} }
triggerChange(); triggerChange();
}; };
export const createMediaChangeHandler = (
form: UseFormResult<{ media: string[] }>,
triggerChange: () => void,
) => (ids: string[]) => {
form.change({
target: {
name: "media",
value: ids,
},
});
triggerChange();
};
export const handleAssignMedia = async <
T extends Pick<ProductVariantFragment, "id" | "media">
>(
media: string[],
variant: T,
assignMedia: (
variables: VariantMediaAssignMutationVariables,
) => Promise<FetchResult<VariantMediaAssignMutation>>,
unassignMedia: (
variables: VariantMediaUnassignMutationVariables,
) => Promise<FetchResult<VariantMediaUnassignMutation>>,
) => {
const { added, removed } = diff(
variant.media.map(mediaObj => mediaObj.id),
media,
);
const assignResults = await Promise.all(
added.map(mediaId =>
assignMedia({
mediaId,
variantId: variant.id,
}),
),
);
const unassignResults = await Promise.all(
removed.map(mediaId =>
unassignMedia({
mediaId,
variantId: variant.id,
}),
),
);
const assignErrors = assignResults.reduce(
(errors, result) => [
...errors,
...(result.data?.variantMediaAssign.errors || []),
],
[],
);
const unassignErrors = unassignResults.reduce(
(errors, result) => [
...errors,
...(result.data?.variantMediaUnassign.errors || []),
],
[],
);
return [...assignErrors, ...unassignErrors];
};

View file

@ -36,6 +36,7 @@ import useShop from "@saleor/hooks/useShop";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { weight } from "@saleor/misc"; import { weight } from "@saleor/misc";
import { getAttributeInputFromVariant } from "@saleor/products/utils/data"; import { getAttributeInputFromVariant } from "@saleor/products/utils/data";
import { handleAssignMedia } from "@saleor/products/utils/handlers";
import usePageSearch from "@saleor/searches/usePageSearch"; import usePageSearch from "@saleor/searches/usePageSearch";
import useProductSearch from "@saleor/searches/useProductSearch"; import useProductSearch from "@saleor/searches/useProductSearch";
import useAttributeValueSearchHandler from "@saleor/utils/handlers/attributeValueSearchHandler"; import useAttributeValueSearchHandler from "@saleor/utils/handlers/attributeValueSearchHandler";
@ -181,26 +182,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
reorderProductVariantsOpts.loading || reorderProductVariantsOpts.loading ||
deleteAttributeValueOpts.loading; deleteAttributeValueOpts.loading;
const handleMediaSelect = (id: string) => () => {
if (variant) {
if (variant?.media?.map(media_obj => media_obj.id).indexOf(id) !== -1) {
unassignMedia({
variables: {
mediaId: id,
variantId: variant.id,
},
});
} else {
assignMedia({
variables: {
mediaId: id,
variantId: variant.id,
},
});
}
}
};
const handleUpdate = async (data: ProductVariantUpdateSubmitData) => { const handleUpdate = async (data: ProductVariantUpdateSubmitData) => {
const uploadFilesResult = await handleUploadMultipleFiles( const uploadFilesResult = await handleUploadMultipleFiles(
data.attributesWithNewFileValue, data.attributesWithNewFileValue,
@ -218,6 +199,13 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
uploadFilesResult, uploadFilesResult,
); );
const assignMediaErrors = await handleAssignMedia(
data.media,
variant,
variables => assignMedia({ variables }),
variables => unassignMedia({ variables }),
);
const result = await updateVariant({ const result = await updateVariant({
variables: { variables: {
addStocks: data.addStocks.map(mapFormsetStockToStockInput), addStocks: data.addStocks.map(mapFormsetStockToStockInput),
@ -255,6 +243,7 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
...result.data?.productVariantStocksDelete.errors, ...result.data?.productVariantStocksDelete.errors,
...result.data?.productVariantStocksUpdate.errors, ...result.data?.productVariantStocksUpdate.errors,
...result.data?.productVariantUpdate.errors, ...result.data?.productVariantUpdate.errors,
...assignMediaErrors,
...channelErrors, ...channelErrors,
]; ];
}; };
@ -340,7 +329,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
header={variant?.name || variant?.sku} header={variant?.name || variant?.sku}
warehouses={mapEdgesToItems(warehouses?.data?.warehouses) || []} warehouses={mapEdgesToItems(warehouses?.data?.warehouses) || []}
onDelete={() => openModal("remove")} onDelete={() => openModal("remove")}
onMediaSelect={handleMediaSelect}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onWarehouseConfigure={() => navigate(warehouseAddPath)} onWarehouseConfigure={() => navigate(warehouseAddPath)}
onVariantPreorderDeactivate={handleDeactivateVariantPreorder} onVariantPreorderDeactivate={handleDeactivateVariantPreorder}

View file

@ -19,7 +19,7 @@ storiesOf("Products / ProductVariantImageSelectDialog", module)
media={variantProductImages} media={variantProductImages}
selectedMedia={variantImages.map(image => image.id)} selectedMedia={variantImages.map(image => image.id)}
onClose={() => undefined} onClose={() => undefined}
onMediaSelect={() => undefined} onConfirm={() => undefined}
open={true} open={true}
/> />
)); ));

View file

@ -25,7 +25,6 @@ storiesOf("Views / Products / Product variant details", module)
variant={variant} variant={variant}
onDelete={undefined} onDelete={undefined}
onSetDefaultVariant={() => undefined} onSetDefaultVariant={() => undefined}
onMediaSelect={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantReorder={() => undefined} onVariantReorder={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
@ -54,7 +53,6 @@ storiesOf("Views / Products / Product variant details", module)
placeholderImage={placeholderImage} placeholderImage={placeholderImage}
onDelete={undefined} onDelete={undefined}
onSetDefaultVariant={() => undefined} onSetDefaultVariant={() => undefined}
onMediaSelect={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantReorder={() => undefined} onVariantReorder={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
@ -82,7 +80,6 @@ storiesOf("Views / Products / Product variant details", module)
variant={variant} variant={variant}
onDelete={undefined} onDelete={undefined}
onSetDefaultVariant={() => undefined} onSetDefaultVariant={() => undefined}
onMediaSelect={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantReorder={() => undefined} onVariantReorder={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
@ -108,7 +105,6 @@ storiesOf("Views / Products / Product variant details", module)
variant={variant} variant={variant}
onDelete={undefined} onDelete={undefined}
onSetDefaultVariant={() => undefined} onSetDefaultVariant={() => undefined}
onMediaSelect={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantReorder={() => undefined} onVariantReorder={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"