Merge pull request #764 from mirumee/fix/price-errors-1244
Do not allow negative values and display errors
This commit is contained in:
commit
32de699216
10 changed files with 69 additions and 52 deletions
|
@ -74,6 +74,10 @@ export const PriceField: React.FC<PriceFieldProps> = props => {
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
),
|
),
|
||||||
|
inputProps: {
|
||||||
|
min: 0,
|
||||||
|
...InputProps?.inputProps
|
||||||
|
},
|
||||||
type: "number"
|
type: "number"
|
||||||
}}
|
}}
|
||||||
name={name}
|
name={name}
|
||||||
|
|
|
@ -62,11 +62,6 @@ const ProductPricing: React.FC<ProductPricingProps> = props => {
|
||||||
value={data.basePrice}
|
value={data.basePrice}
|
||||||
currencySymbol={currency}
|
currencySymbol={currency}
|
||||||
onChange={handlePriceChange}
|
onChange={handlePriceChange}
|
||||||
InputProps={{
|
|
||||||
inputProps: {
|
|
||||||
min: 0
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import CardTitle from "@saleor/components/CardTitle";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
import { ProductErrorFragment } from "@saleor/fragments/types/ProductErrorFragment";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
|
import createNonNegativeValueChangeHandler from "@saleor/utils/handlers/nonNegativeValueChangeHandler";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ const ProductShipping: React.FC<ProductShippingProps> = props => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["weight"], errors);
|
const formErrors = getFormErrors(["weight"], errors);
|
||||||
|
const handleChange = createNonNegativeValueChangeHandler(onChange);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
|
@ -46,7 +48,7 @@ const ProductShipping: React.FC<ProductShippingProps> = props => {
|
||||||
helperText={getProductErrorMessage(formErrors.weight, intl)}
|
helperText={getProductErrorMessage(formErrors.weight, intl)}
|
||||||
name="weight"
|
name="weight"
|
||||||
value={data.weight}
|
value={data.weight}
|
||||||
onChange={onChange}
|
onChange={handleChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
|
||||||
import { renderCollection } from "@saleor/misc";
|
import { renderCollection } from "@saleor/misc";
|
||||||
import { ICONBUTTON_SIZE } from "@saleor/theme";
|
import { ICONBUTTON_SIZE } from "@saleor/theme";
|
||||||
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors";
|
||||||
|
import createNonNegativeValueChangeHandler from "@saleor/utils/handlers/nonNegativeValueChangeHandler";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -238,33 +239,41 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{renderCollection(stocks, stock => (
|
{renderCollection(stocks, stock => {
|
||||||
<TableRow key={stock.id}>
|
const handleQuantityChange = createNonNegativeValueChangeHandler(
|
||||||
<TableCell className={classes.colName}>{stock.label}</TableCell>
|
event => onChange(stock.id, event.target.value)
|
||||||
<TableCell className={classes.colQuantity}>
|
);
|
||||||
<TextField
|
|
||||||
className={classes.inputComponent}
|
return (
|
||||||
disabled={disabled}
|
<TableRow key={stock.id}>
|
||||||
fullWidth
|
<TableCell className={classes.colName}>
|
||||||
inputProps={{
|
{stock.label}
|
||||||
className: classes.input,
|
</TableCell>
|
||||||
min: 0,
|
<TableCell className={classes.colQuantity}>
|
||||||
type: "number"
|
<TextField
|
||||||
}}
|
className={classes.inputComponent}
|
||||||
onChange={event => onChange(stock.id, event.target.value)}
|
disabled={disabled}
|
||||||
value={stock.value}
|
fullWidth
|
||||||
/>
|
inputProps={{
|
||||||
</TableCell>
|
className: classes.input,
|
||||||
<TableCell className={classes.colAction}>
|
min: 0,
|
||||||
<IconButton
|
type: "number"
|
||||||
color="primary"
|
}}
|
||||||
onClick={() => onWarehouseStockDelete(stock.id)}
|
onChange={handleQuantityChange}
|
||||||
>
|
value={stock.value}
|
||||||
<DeleteIcon />
|
/>
|
||||||
</IconButton>
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell className={classes.colAction}>
|
||||||
</TableRow>
|
<IconButton
|
||||||
))}
|
color="primary"
|
||||||
|
onClick={() => onWarehouseStockDelete(stock.id)}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{warehousesToAssign.length > 0 && (
|
{warehousesToAssign.length > 0 && (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={2}>
|
<TableCell colSpan={2}>
|
||||||
|
|
|
@ -96,7 +96,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
||||||
|
|
||||||
const initialForm: ProductVariantCreatePageFormData = {
|
const initialForm: ProductVariantCreatePageFormData = {
|
||||||
costPrice: "",
|
costPrice: "",
|
||||||
images: maybe(() => product.images.map(image => image.id)),
|
images: product?.images.map(image => image.id),
|
||||||
metadata: [],
|
metadata: [],
|
||||||
price: "",
|
price: "",
|
||||||
privateMetadata: [],
|
privateMetadata: [],
|
||||||
|
@ -148,10 +148,9 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<ProductVariantPrice
|
<ProductVariantPrice
|
||||||
|
data={data}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
price={data.price}
|
|
||||||
currencySymbol={currencySymbol}
|
currencySymbol={currencySymbol}
|
||||||
costPrice={data.costPrice}
|
|
||||||
loading={disabled}
|
loading={disabled}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -136,12 +136,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialForm: ProductVariantPageFormData = {
|
const initialForm: ProductVariantPageFormData = {
|
||||||
costPrice: maybe(() => variant.costPrice.amount.toString(), ""),
|
costPrice: variant?.costPrice?.amount.toString() || "",
|
||||||
metadata: variant?.metadata?.map(mapMetadataItemToInput),
|
metadata: variant?.metadata?.map(mapMetadataItemToInput),
|
||||||
price: maybe(() => variant.price.amount.toString(), ""),
|
price: variant?.price?.amount.toString() || "",
|
||||||
privateMetadata: variant?.privateMetadata?.map(mapMetadataItemToInput),
|
privateMetadata: variant?.privateMetadata?.map(mapMetadataItemToInput),
|
||||||
sku: maybe(() => variant.sku, ""),
|
sku: variant?.sku || "",
|
||||||
trackInventory: variant?.trackInventory,
|
trackInventory: !!variant?.trackInventory,
|
||||||
weight: variant?.weight?.value.toString() || ""
|
weight: variant?.weight?.value.toString() || ""
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<ProductVariantPrice
|
<ProductVariantPrice
|
||||||
errors={errors}
|
errors={errors}
|
||||||
price={data.price}
|
data={data}
|
||||||
currencySymbol={
|
currencySymbol={
|
||||||
variant && variant.price
|
variant && variant.price
|
||||||
? variant.price.currency
|
? variant.price.currency
|
||||||
|
@ -236,7 +236,6 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
|
||||||
? variant.costPrice.currency
|
? variant.costPrice.currency
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
costPrice={data.costPrice}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -22,20 +22,19 @@ const useStyles = makeStyles(
|
||||||
|
|
||||||
interface ProductVariantPriceProps {
|
interface ProductVariantPriceProps {
|
||||||
currencySymbol?: string;
|
currencySymbol?: string;
|
||||||
price?: string;
|
data: Record<"price" | "costPrice", string>;
|
||||||
costPrice?: string;
|
|
||||||
errors: ProductErrorFragment[];
|
errors: ProductErrorFragment[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
onChange(event: any);
|
onChange(event: any);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
const { currencySymbol, costPrice, errors, price, loading, onChange } = props;
|
const { currencySymbol, data, errors, loading, onChange } = props;
|
||||||
|
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["price", "cost_price"], errors);
|
const formErrors = getFormErrors(["price", "costPrice"], errors);
|
||||||
|
|
||||||
const handlePriceChange = createNonNegativeValueChangeHandler(onChange);
|
const handlePriceChange = createNonNegativeValueChangeHandler(onChange);
|
||||||
|
|
||||||
|
@ -52,11 +51,12 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
<div>
|
<div>
|
||||||
<PriceField
|
<PriceField
|
||||||
error={!!formErrors.price}
|
error={!!formErrors.price}
|
||||||
|
hint={getProductErrorMessage(formErrors.price, intl)}
|
||||||
name="price"
|
name="price"
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
defaultMessage: "Price"
|
defaultMessage: "Price"
|
||||||
})}
|
})}
|
||||||
value={price}
|
value={data.price}
|
||||||
currencySymbol={currencySymbol}
|
currencySymbol={currencySymbol}
|
||||||
onChange={handlePriceChange}
|
onChange={handlePriceChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
@ -69,20 +69,20 @@ const ProductVariantPrice: React.FC<ProductVariantPriceProps> = props => {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<PriceField
|
<PriceField
|
||||||
error={!!formErrors.cost_price}
|
error={!!formErrors.costPrice}
|
||||||
name="costPrice"
|
name="costPrice"
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
defaultMessage: "Cost price"
|
defaultMessage: "Cost price"
|
||||||
})}
|
})}
|
||||||
hint={
|
hint={
|
||||||
getProductErrorMessage(formErrors.cost_price, intl) ||
|
getProductErrorMessage(formErrors.costPrice, intl) ||
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
defaultMessage: "Optional",
|
defaultMessage: "Optional",
|
||||||
description: "optional field",
|
description: "optional field",
|
||||||
id: "productVariantPriceOptionalCostPriceField"
|
id: "productVariantPriceOptionalCostPriceField"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
value={costPrice}
|
value={data.costPrice}
|
||||||
currencySymbol={currencySymbol}
|
currencySymbol={currencySymbol}
|
||||||
onChange={handlePriceChange}
|
onChange={handlePriceChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
|
|
@ -238,7 +238,7 @@ export function mapFormsetStockToStockInput(
|
||||||
stock: FormsetAtomicData<null, string>
|
stock: FormsetAtomicData<null, string>
|
||||||
): StockInput {
|
): StockInput {
|
||||||
return {
|
return {
|
||||||
quantity: parseInt(stock.value, 10),
|
quantity: parseInt(stock.value, 10) || 0,
|
||||||
warehouse: stock.id
|
warehouse: stock.id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
||||||
product: productId,
|
product: productId,
|
||||||
sku: formData.sku,
|
sku: formData.sku,
|
||||||
stocks: formData.stocks.map(stock => ({
|
stocks: formData.stocks.map(stock => ({
|
||||||
quantity: parseInt(stock.value, 0),
|
quantity: parseInt(stock.value, 0) || 0,
|
||||||
warehouse: stock.id
|
warehouse: stock.id
|
||||||
})),
|
})),
|
||||||
trackInventory: true,
|
trackInventory: true,
|
||||||
|
|
|
@ -7178,6 +7178,7 @@ exports[`Storyshots Generics / Price input disabled 1`] = `
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-disabled-id MuiOutlinedInput-disabled-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
disabled=""
|
disabled=""
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7223,6 +7224,7 @@ exports[`Storyshots Generics / Price input with currency symbol 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7276,6 +7278,7 @@ exports[`Storyshots Generics / Price input with hint 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7332,6 +7335,7 @@ exports[`Storyshots Generics / Price input with label 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7383,6 +7387,7 @@ exports[`Storyshots Generics / Price input with label and hint 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7433,6 +7438,7 @@ exports[`Storyshots Generics / Price input with no value 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
|
@ -7478,6 +7484,7 @@ exports[`Storyshots Generics / Price input with value 1`] = `
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
value="30"
|
value="30"
|
||||||
|
@ -7530,6 +7537,7 @@ exports[`Storyshots Generics / Price input with value, label, currency symbol an
|
||||||
<input
|
<input
|
||||||
aria-invalid="true"
|
aria-invalid="true"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
value="30"
|
value="30"
|
||||||
|
@ -7595,6 +7603,7 @@ exports[`Storyshots Generics / Price input with value, label, currency symbol an
|
||||||
<input
|
<input
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
class="MuiInputBase-input-id MuiOutlinedInput-input-id MuiInputBase-inputAdornedEnd-id MuiOutlinedInput-inputAdornedEnd-id"
|
||||||
|
min="0"
|
||||||
name="price"
|
name="price"
|
||||||
type="number"
|
type="number"
|
||||||
value="30"
|
value="30"
|
||||||
|
|
Loading…
Reference in a new issue