Add current usage on vouchers (#1519) (#1576)

* wip design label

* add usesLeft calculation

* snapshots & messages

* fix type errors

* add error on input and disable save button when value is invalid

* resetting input value to initial after checkbox state change

* remove uses left on new vouchers & set initial value to 1
This commit is contained in:
Michał Droń 2021-11-10 13:05:56 +01:00 committed by GitHub
parent 4f18947dda
commit 3de07a4f3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 115 additions and 44 deletions

View file

@ -3123,6 +3123,10 @@
"context": "voucher usage limit, header",
"string": "Usage Limit"
},
"src_dot_discounts_dot_components_dot_VoucherLimits_dot_usesLeftCaption": {
"context": "usage limit uses left caption",
"string": "Uses left"
},
"src_dot_discounts_dot_components_dot_VoucherListPage_dot_1112241061": {
"context": "tab name",
"string": "All Vouchers"

View file

@ -45,7 +45,8 @@ export interface FormData extends MetadataFormData {
startDate: string;
startTime: string;
type: VoucherTypeEnum;
usageLimit: string;
usageLimit: number;
used: number;
value: number;
}
@ -95,7 +96,8 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
startDate: "",
startTime: "",
type: VoucherTypeEnum.ENTIRE_ORDER,
usageLimit: "0",
usageLimit: 1,
used: 0,
value: 0,
metadata: [],
privateMetadata: []
@ -103,7 +105,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit, triggerChange }) => {
{({ change, data, hasChanged, submit, triggerChange, set }) => {
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
change
);
@ -173,9 +175,12 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
<CardSpacer />
<VoucherLimits
data={data}
initialUsageLimit={initialForm.usageLimit}
disabled={disabled}
errors={errors}
onChange={change}
setData={set}
isNewVoucher
/>
<CardSpacer />
<VoucherDates

View file

@ -75,7 +75,8 @@ export interface VoucherDetailsPageFormData extends MetadataFormData {
startDate: string;
startTime: string;
type: VoucherTypeEnum;
usageLimit: string;
usageLimit: number;
used: number;
}
export interface VoucherDetailsPageProps
@ -192,14 +193,15 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
startDate: splitDateTime(voucher?.startDate ?? "").date,
startTime: splitDateTime(voucher?.startDate ?? "").time,
type: voucher?.type ?? VoucherTypeEnum.ENTIRE_ORDER,
usageLimit: voucher?.usageLimit?.toString() ?? "0",
usageLimit: voucher?.usageLimit ?? 1,
used: voucher?.used ?? 0,
metadata: voucher?.metadata.map(mapMetadataItemToInput),
privateMetadata: voucher?.privateMetadata.map(mapMetadataItemToInput)
};
return (
<Form initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit, triggerChange }) => {
{({ change, data, hasChanged, submit, triggerChange, set }) => {
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
change
);
@ -209,13 +211,14 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
triggerChange
);
const formDisabled =
data.discountType.toString() !== "SHIPPING" &&
data.channelListings?.some(
channel =>
validatePrice(channel.discountValue) ||
(data.requirementsPicker === RequirementsPicker.ORDER &&
validatePrice(channel.minSpent))
);
(data.discountType.toString() !== "SHIPPING" &&
data.channelListings?.some(
channel =>
validatePrice(channel.discountValue) ||
(data.requirementsPicker === RequirementsPicker.ORDER &&
validatePrice(channel.minSpent))
)) ||
data.usageLimit <= 0;
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -399,9 +402,12 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
<CardSpacer />
<VoucherLimits
data={data}
initialUsageLimit={initialForm.usageLimit}
disabled={disabled}
errors={errors}
onChange={change}
setData={set}
isNewVoucher={false}
/>
<CardSpacer />
<DiscountDates

View file

@ -1,6 +1,7 @@
import { Card, CardContent, TextField } from "@material-ui/core";
import { Card, CardContent, TextField, Typography } from "@material-ui/core";
import CardTitle from "@saleor/components/CardTitle";
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import { Grid } from "@saleor/components/Grid";
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
import { getFormErrors } from "@saleor/utils/errors";
import getDiscountErrorMessage from "@saleor/utils/errors/discounts";
@ -9,50 +10,89 @@ import { useIntl } from "react-intl";
import { VoucherDetailsPageFormData } from "../VoucherDetailsPage";
import messages from "./messages";
import { useStyles } from "./styles";
interface VoucherLimitsProps {
data: VoucherDetailsPageFormData;
disabled: boolean;
errors: DiscountErrorFragment[];
initialUsageLimit: number;
onChange: (event: React.ChangeEvent<any>) => void;
setData: (data: Partial<VoucherDetailsPageFormData>) => void;
isNewVoucher: boolean;
}
const VoucherLimits = ({
data,
disabled,
errors,
onChange
initialUsageLimit,
onChange,
setData,
isNewVoucher
}: VoucherLimitsProps) => {
const intl = useIntl();
const classes = useStyles();
const formErrors = getFormErrors(["usageLimit"], errors);
const usesLeft = data.usageLimit - data.used;
return (
<Card>
<CardTitle title={intl.formatMessage(messages.usageLimitsTitle)} />
<CardContent>
<CardContent className={classes.cardContent}>
<ControlledCheckbox
checked={data.hasUsageLimit}
label={intl.formatMessage(messages.hasUsageLimit)}
name={"hasUsageLimit" as keyof VoucherDetailsPageFormData}
onChange={onChange}
onChange={evt => {
onChange(evt);
setData({ usageLimit: initialUsageLimit });
}}
/>
{data.hasUsageLimit && (
<TextField
disabled={disabled}
error={!!formErrors.usageLimit}
helperText={getDiscountErrorMessage(formErrors.usageLimit, intl)}
label={intl.formatMessage(messages.usageLimit)}
name={"usageLimit" as keyof VoucherDetailsPageFormData}
value={data.usageLimit}
onChange={onChange}
type="number"
inputProps={{
min: 0
}}
fullWidth
/>
)}
{data.hasUsageLimit &&
(isNewVoucher ? (
<TextField
disabled={disabled}
error={!!formErrors.usageLimit || data.usageLimit <= 0}
helperText={getDiscountErrorMessage(formErrors.usageLimit, intl)}
label={intl.formatMessage(messages.usageLimit)}
name={"usageLimit" as keyof VoucherDetailsPageFormData}
value={data.usageLimit}
onChange={onChange}
type="number"
fullWidth
inputProps={{
min: 1
}}
/>
) : (
<Grid variant="uniform">
<TextField
disabled={disabled}
error={!!formErrors.usageLimit || data.usageLimit <= 0}
helperText={getDiscountErrorMessage(
formErrors.usageLimit,
intl
)}
label={intl.formatMessage(messages.usageLimit)}
name={"usageLimit" as keyof VoucherDetailsPageFormData}
value={data.usageLimit}
onChange={onChange}
type="number"
inputProps={{
min: 1
}}
/>
<div className={classes.usesLeftLabelWrapper}>
<Typography variant="caption">
{intl.formatMessage(messages.usesLeftCaption)}
</Typography>
<Typography>{usesLeft >= 0 ? usesLeft : 0}</Typography>
</div>
</Grid>
))}
<ControlledCheckbox
checked={data.applyOncePerCustomer}
label={intl.formatMessage(messages.applyOncePerCustomer)}

View file

@ -13,6 +13,10 @@ export default defineMessages({
defaultMessage: "Limit of Uses",
description: "limit voucher"
},
usesLeftCaption: {
defaultMessage: "Uses left",
description: "usage limit uses left caption"
},
applyOncePerCustomer: {
defaultMessage: "Limit to one use per customer",
description: "limit voucher"

View file

@ -0,0 +1,16 @@
import { makeStyles } from "@saleor/macaw-ui";
export const useStyles = makeStyles(
() => ({
cardContent: {
display: "flex",
flexDirection: "column"
},
usesLeftLabelWrapper: {
display: "flex",
flexDirection: "column",
flex: 1
}
}),
{ name: "VoucherLimits" }
);

View file

@ -49,9 +49,7 @@ export function createHandler(
formData.discountType === DiscountTypeEnum.SHIPPING
? VoucherTypeEnum.SHIPPING
: formData.type,
usageLimit: formData.hasUsageLimit
? parseInt(formData.usageLimit, 10)
: null
usageLimit: formData.hasUsageLimit ? formData.usageLimit : null
}
});

View file

@ -56,9 +56,7 @@ export function createUpdateHandler(
formData.discountType === DiscountTypeEnum.SHIPPING
? VoucherTypeEnum.SHIPPING
: formData.type,
usageLimit: formData.hasUsageLimit
? parseInt(formData.usageLimit, 10)
: null
usageLimit: formData.hasUsageLimit ? formData.usageLimit : null
}
}).then(({ data }) => data?.voucherUpdate.errors ?? []),

View file

@ -99037,7 +99037,7 @@ exports[`Storyshots Views / Discounts / Voucher create default 1`] = `
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
>
<label
class="MuiFormControlLabel-root-id"
@ -100597,7 +100597,7 @@ exports[`Storyshots Views / Discounts / Voucher create form errors 1`] = `
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
>
<label
class="MuiFormControlLabel-root-id"
@ -102790,7 +102790,7 @@ exports[`Storyshots Views / Discounts / Voucher details default 1`] = `
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
>
<label
class="MuiFormControlLabel-root-id"
@ -105165,7 +105165,7 @@ exports[`Storyshots Views / Discounts / Voucher details form errors 1`] = `
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
>
<label
class="MuiFormControlLabel-root-id"
@ -107103,7 +107103,7 @@ exports[`Storyshots Views / Discounts / Voucher details loading 1`] = `
class="CardTitle-hr-id"
/>
<div
class="MuiCardContent-root-id"
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
>
<label
class="MuiFormControlLabel-root-id"