Merge pull request #764 from mirumee/fix/price-errors-1244

Do not allow negative values and display errors
This commit is contained in:
Dominik Żegleń 2020-10-16 10:12:47 +02:00 committed by GitHub
commit 32de699216
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 52 deletions

View file

@ -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}

View file

@ -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>

View file

@ -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">

View file

@ -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}>

View file

@ -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}
/> />

View file

@ -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}
/> />

View file

@ -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}

View file

@ -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
}; };
} }

View file

@ -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,

View file

@ -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"