Fix product creation flow for simple products (#4081)
* Fix simple product creation * Fix simple product creation
This commit is contained in:
parent
3c1d84f775
commit
498feff0a2
4 changed files with 91 additions and 11 deletions
|
@ -233,7 +233,6 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
assignReferencesAttributeId,
|
assignReferencesAttributeId,
|
||||||
data.attributes,
|
data.attributes,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailPageLayout>
|
<DetailPageLayout>
|
||||||
<TopNav href={productListUrl()} title={header} />
|
<TopNav href={productListUrl()} title={header} />
|
||||||
|
@ -275,7 +274,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<ProductVariantPrice
|
<ProductVariantPrice
|
||||||
ProductVariantChannelListings={data.channelListings}
|
ProductVariantChannelListings={data.channelListings}
|
||||||
errors={channelsErrors}
|
errors={[...errors, ...channelsErrors]}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={handlers.changeChannelPrice}
|
onChange={handlers.changeChannelPrice}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -9,11 +9,15 @@ import PriceField from "@dashboard/components/PriceField";
|
||||||
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
import ResponsiveTable from "@dashboard/components/ResponsiveTable";
|
||||||
import Skeleton from "@dashboard/components/Skeleton";
|
import Skeleton from "@dashboard/components/Skeleton";
|
||||||
import TableRowLink from "@dashboard/components/TableRowLink";
|
import TableRowLink from "@dashboard/components/TableRowLink";
|
||||||
import { ProductChannelListingErrorFragment } from "@dashboard/graphql";
|
import {
|
||||||
|
ProductChannelListingErrorFragment,
|
||||||
|
ProductErrorFragment,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
import { renderCollection } from "@dashboard/misc";
|
import { renderCollection } from "@dashboard/misc";
|
||||||
import {
|
import {
|
||||||
getFormChannelError,
|
getFormChannelError,
|
||||||
getFormChannelErrors,
|
getFormChannelErrors,
|
||||||
|
getFormErrors,
|
||||||
} from "@dashboard/utils/errors";
|
} from "@dashboard/utils/errors";
|
||||||
import getProductErrorMessage from "@dashboard/utils/errors/product";
|
import getProductErrorMessage from "@dashboard/utils/errors/product";
|
||||||
import { TableBody, TableCell, TableHead } from "@material-ui/core";
|
import { TableBody, TableCell, TableHead } from "@material-ui/core";
|
||||||
|
@ -23,7 +27,7 @@ import { FormattedMessage, MessageDescriptor, useIntl } from "react-intl";
|
||||||
|
|
||||||
interface ProductVariantPriceProps {
|
interface ProductVariantPriceProps {
|
||||||
ProductVariantChannelListings?: ChannelData[];
|
ProductVariantChannelListings?: ChannelData[];
|
||||||
errors?: ProductChannelListingErrorFragment[];
|
errors: Array<ProductErrorFragment | ProductChannelListingErrorFragment>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange?: (
|
onChange?: (
|
||||||
|
@ -47,7 +51,10 @@ export const ProductVariantPrice: React.FC<
|
||||||
disabledMessage,
|
disabledMessage,
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const formErrors = getFormChannelErrors(["price", "costPrice"], errors);
|
const channelErrors = errors.filter(
|
||||||
|
e => "channels" in e,
|
||||||
|
) as ProductChannelListingErrorFragment[];
|
||||||
|
const apiErrors = getFormChannelErrors(["price", "costPrice"], channelErrors);
|
||||||
|
|
||||||
if (disabled || !ProductVariantChannelListings.length) {
|
if (disabled || !ProductVariantChannelListings.length) {
|
||||||
return (
|
return (
|
||||||
|
@ -120,12 +127,15 @@ export const ProductVariantPrice: React.FC<
|
||||||
{renderCollection(
|
{renderCollection(
|
||||||
ProductVariantChannelListings,
|
ProductVariantChannelListings,
|
||||||
(listing, index) => {
|
(listing, index) => {
|
||||||
const priceError = getFormChannelError(
|
const fieldName = `${listing.id}-channel-price`;
|
||||||
formErrors.price,
|
const formErrors = getFormErrors([fieldName], errors);
|
||||||
listing.id,
|
|
||||||
);
|
const priceError =
|
||||||
|
getFormChannelError(apiErrors.price, listing.id) ||
|
||||||
|
formErrors[fieldName];
|
||||||
|
|
||||||
const costPriceError = getFormChannelError(
|
const costPriceError = getFormChannelError(
|
||||||
formErrors.costPrice,
|
apiErrors.costPrice,
|
||||||
listing.id,
|
listing.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -142,7 +152,7 @@ export const ProductVariantPrice: React.FC<
|
||||||
id: "b1zuN9",
|
id: "b1zuN9",
|
||||||
defaultMessage: "Price",
|
defaultMessage: "Price",
|
||||||
})}
|
})}
|
||||||
name={`${listing.id}-channel-price`}
|
name={fieldName}
|
||||||
value={listing.price || ""}
|
value={listing.price || ""}
|
||||||
currencySymbol={listing.currency}
|
currencySymbol={listing.currency}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
|
|
63
src/products/utils/validation.test.ts
Normal file
63
src/products/utils/validation.test.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { ProductCreateData } from "../components/ProductCreatePage";
|
||||||
|
import { validateProductCreateData } from "./validation";
|
||||||
|
|
||||||
|
describe("validateProductCreateData", () => {
|
||||||
|
it("returns errors when there is no productType or name", () => {
|
||||||
|
// Arrange
|
||||||
|
const data = { productType: "" } as unknown as ProductCreateData;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const errors = validateProductCreateData(data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
__typename: "ProductError",
|
||||||
|
attributes: [],
|
||||||
|
code: "REQUIRED",
|
||||||
|
field: "productType",
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: "ProductError",
|
||||||
|
attributes: [],
|
||||||
|
code: "REQUIRED",
|
||||||
|
field: "name",
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns errors when there is no prices for channels", () => {
|
||||||
|
// Arrange
|
||||||
|
const data = {
|
||||||
|
productType: "something",
|
||||||
|
name: "something",
|
||||||
|
channelListings: [
|
||||||
|
{ id: "chann-1", price: "" },
|
||||||
|
{ id: "chann-2", price: "" },
|
||||||
|
],
|
||||||
|
} as unknown as ProductCreateData;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const errors = validateProductCreateData(data);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(errors).toEqual([
|
||||||
|
{
|
||||||
|
__typename: "ProductError",
|
||||||
|
attributes: [],
|
||||||
|
code: "REQUIRED",
|
||||||
|
field: "chann-1-channel-price",
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__typename: "ProductError",
|
||||||
|
attributes: [],
|
||||||
|
code: "REQUIRED",
|
||||||
|
field: "chann-2-channel-price",
|
||||||
|
message: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -34,6 +34,14 @@ export const validateProductCreateData = (data: ProductCreateData) => {
|
||||||
errors = [...errors, createEmptyRequiredError("name")];
|
errors = [...errors, createEmptyRequiredError("name")];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.channelListings) {
|
||||||
|
const emptyPrices = data.channelListings
|
||||||
|
.filter(channel => channel.price?.length === 0)
|
||||||
|
.map(({ id }) => createEmptyRequiredError(`${id}-channel-price`));
|
||||||
|
|
||||||
|
errors = [...errors, ...emptyPrices];
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue