* 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:
parent
4f18947dda
commit
3de07a4f3b
9 changed files with 115 additions and 44 deletions
|
@ -3123,6 +3123,10 @@
|
||||||
"context": "voucher usage limit, header",
|
"context": "voucher usage limit, header",
|
||||||
"string": "Usage Limit"
|
"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": {
|
"src_dot_discounts_dot_components_dot_VoucherListPage_dot_1112241061": {
|
||||||
"context": "tab name",
|
"context": "tab name",
|
||||||
"string": "All Vouchers"
|
"string": "All Vouchers"
|
||||||
|
|
|
@ -45,7 +45,8 @@ export interface FormData extends MetadataFormData {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
type: VoucherTypeEnum;
|
type: VoucherTypeEnum;
|
||||||
usageLimit: string;
|
usageLimit: number;
|
||||||
|
used: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +96,8 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
startDate: "",
|
startDate: "",
|
||||||
startTime: "",
|
startTime: "",
|
||||||
type: VoucherTypeEnum.ENTIRE_ORDER,
|
type: VoucherTypeEnum.ENTIRE_ORDER,
|
||||||
usageLimit: "0",
|
usageLimit: 1,
|
||||||
|
used: 0,
|
||||||
value: 0,
|
value: 0,
|
||||||
metadata: [],
|
metadata: [],
|
||||||
privateMetadata: []
|
privateMetadata: []
|
||||||
|
@ -103,7 +105,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
||||||
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
||||||
change
|
change
|
||||||
);
|
);
|
||||||
|
@ -173,9 +175,12 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<VoucherLimits
|
<VoucherLimits
|
||||||
data={data}
|
data={data}
|
||||||
|
initialUsageLimit={initialForm.usageLimit}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
setData={set}
|
||||||
|
isNewVoucher
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<VoucherDates
|
<VoucherDates
|
||||||
|
|
|
@ -75,7 +75,8 @@ export interface VoucherDetailsPageFormData extends MetadataFormData {
|
||||||
startDate: string;
|
startDate: string;
|
||||||
startTime: string;
|
startTime: string;
|
||||||
type: VoucherTypeEnum;
|
type: VoucherTypeEnum;
|
||||||
usageLimit: string;
|
usageLimit: number;
|
||||||
|
used: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VoucherDetailsPageProps
|
export interface VoucherDetailsPageProps
|
||||||
|
@ -192,14 +193,15 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
||||||
startDate: splitDateTime(voucher?.startDate ?? "").date,
|
startDate: splitDateTime(voucher?.startDate ?? "").date,
|
||||||
startTime: splitDateTime(voucher?.startDate ?? "").time,
|
startTime: splitDateTime(voucher?.startDate ?? "").time,
|
||||||
type: voucher?.type ?? VoucherTypeEnum.ENTIRE_ORDER,
|
type: voucher?.type ?? VoucherTypeEnum.ENTIRE_ORDER,
|
||||||
usageLimit: voucher?.usageLimit?.toString() ?? "0",
|
usageLimit: voucher?.usageLimit ?? 1,
|
||||||
|
used: voucher?.used ?? 0,
|
||||||
metadata: voucher?.metadata.map(mapMetadataItemToInput),
|
metadata: voucher?.metadata.map(mapMetadataItemToInput),
|
||||||
privateMetadata: voucher?.privateMetadata.map(mapMetadataItemToInput)
|
privateMetadata: voucher?.privateMetadata.map(mapMetadataItemToInput)
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
||||||
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
||||||
change
|
change
|
||||||
);
|
);
|
||||||
|
@ -209,13 +211,14 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
||||||
triggerChange
|
triggerChange
|
||||||
);
|
);
|
||||||
const formDisabled =
|
const formDisabled =
|
||||||
data.discountType.toString() !== "SHIPPING" &&
|
(data.discountType.toString() !== "SHIPPING" &&
|
||||||
data.channelListings?.some(
|
data.channelListings?.some(
|
||||||
channel =>
|
channel =>
|
||||||
validatePrice(channel.discountValue) ||
|
validatePrice(channel.discountValue) ||
|
||||||
(data.requirementsPicker === RequirementsPicker.ORDER &&
|
(data.requirementsPicker === RequirementsPicker.ORDER &&
|
||||||
validatePrice(channel.minSpent))
|
validatePrice(channel.minSpent))
|
||||||
);
|
)) ||
|
||||||
|
data.usageLimit <= 0;
|
||||||
const changeMetadata = makeMetadataChangeHandler(change);
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -399,9 +402,12 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<VoucherLimits
|
<VoucherLimits
|
||||||
data={data}
|
data={data}
|
||||||
|
initialUsageLimit={initialForm.usageLimit}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
onChange={change}
|
onChange={change}
|
||||||
|
setData={set}
|
||||||
|
isNewVoucher={false}
|
||||||
/>
|
/>
|
||||||
<CardSpacer />
|
<CardSpacer />
|
||||||
<DiscountDates
|
<DiscountDates
|
||||||
|
|
|
@ -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 CardTitle from "@saleor/components/CardTitle";
|
||||||
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
|
||||||
|
import { Grid } from "@saleor/components/Grid";
|
||||||
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
||||||
import { getFormErrors } from "@saleor/utils/errors";
|
import { getFormErrors } from "@saleor/utils/errors";
|
||||||
import getDiscountErrorMessage from "@saleor/utils/errors/discounts";
|
import getDiscountErrorMessage from "@saleor/utils/errors/discounts";
|
||||||
|
@ -9,50 +10,89 @@ import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { VoucherDetailsPageFormData } from "../VoucherDetailsPage";
|
import { VoucherDetailsPageFormData } from "../VoucherDetailsPage";
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
import { useStyles } from "./styles";
|
||||||
|
|
||||||
interface VoucherLimitsProps {
|
interface VoucherLimitsProps {
|
||||||
data: VoucherDetailsPageFormData;
|
data: VoucherDetailsPageFormData;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: DiscountErrorFragment[];
|
errors: DiscountErrorFragment[];
|
||||||
|
initialUsageLimit: number;
|
||||||
onChange: (event: React.ChangeEvent<any>) => void;
|
onChange: (event: React.ChangeEvent<any>) => void;
|
||||||
|
setData: (data: Partial<VoucherDetailsPageFormData>) => void;
|
||||||
|
isNewVoucher: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VoucherLimits = ({
|
const VoucherLimits = ({
|
||||||
data,
|
data,
|
||||||
disabled,
|
disabled,
|
||||||
errors,
|
errors,
|
||||||
onChange
|
initialUsageLimit,
|
||||||
|
onChange,
|
||||||
|
setData,
|
||||||
|
isNewVoucher
|
||||||
}: VoucherLimitsProps) => {
|
}: VoucherLimitsProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
const formErrors = getFormErrors(["usageLimit"], errors);
|
const formErrors = getFormErrors(["usageLimit"], errors);
|
||||||
|
|
||||||
|
const usesLeft = data.usageLimit - data.used;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle title={intl.formatMessage(messages.usageLimitsTitle)} />
|
<CardTitle title={intl.formatMessage(messages.usageLimitsTitle)} />
|
||||||
<CardContent>
|
<CardContent className={classes.cardContent}>
|
||||||
<ControlledCheckbox
|
<ControlledCheckbox
|
||||||
checked={data.hasUsageLimit}
|
checked={data.hasUsageLimit}
|
||||||
label={intl.formatMessage(messages.hasUsageLimit)}
|
label={intl.formatMessage(messages.hasUsageLimit)}
|
||||||
name={"hasUsageLimit" as keyof VoucherDetailsPageFormData}
|
name={"hasUsageLimit" as keyof VoucherDetailsPageFormData}
|
||||||
onChange={onChange}
|
onChange={evt => {
|
||||||
|
onChange(evt);
|
||||||
|
setData({ usageLimit: initialUsageLimit });
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{data.hasUsageLimit && (
|
{data.hasUsageLimit &&
|
||||||
|
(isNewVoucher ? (
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
error={!!formErrors.usageLimit}
|
error={!!formErrors.usageLimit || data.usageLimit <= 0}
|
||||||
helperText={getDiscountErrorMessage(formErrors.usageLimit, intl)}
|
helperText={getDiscountErrorMessage(formErrors.usageLimit, intl)}
|
||||||
label={intl.formatMessage(messages.usageLimit)}
|
label={intl.formatMessage(messages.usageLimit)}
|
||||||
name={"usageLimit" as keyof VoucherDetailsPageFormData}
|
name={"usageLimit" as keyof VoucherDetailsPageFormData}
|
||||||
value={data.usageLimit}
|
value={data.usageLimit}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
type="number"
|
type="number"
|
||||||
inputProps={{
|
|
||||||
min: 0
|
|
||||||
}}
|
|
||||||
fullWidth
|
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
|
<ControlledCheckbox
|
||||||
checked={data.applyOncePerCustomer}
|
checked={data.applyOncePerCustomer}
|
||||||
label={intl.formatMessage(messages.applyOncePerCustomer)}
|
label={intl.formatMessage(messages.applyOncePerCustomer)}
|
||||||
|
|
|
@ -13,6 +13,10 @@ export default defineMessages({
|
||||||
defaultMessage: "Limit of Uses",
|
defaultMessage: "Limit of Uses",
|
||||||
description: "limit voucher"
|
description: "limit voucher"
|
||||||
},
|
},
|
||||||
|
usesLeftCaption: {
|
||||||
|
defaultMessage: "Uses left",
|
||||||
|
description: "usage limit uses left caption"
|
||||||
|
},
|
||||||
applyOncePerCustomer: {
|
applyOncePerCustomer: {
|
||||||
defaultMessage: "Limit to one use per customer",
|
defaultMessage: "Limit to one use per customer",
|
||||||
description: "limit voucher"
|
description: "limit voucher"
|
||||||
|
|
16
src/discounts/components/VoucherLimits/styles.ts
Normal file
16
src/discounts/components/VoucherLimits/styles.ts
Normal 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" }
|
||||||
|
);
|
|
@ -49,9 +49,7 @@ export function createHandler(
|
||||||
formData.discountType === DiscountTypeEnum.SHIPPING
|
formData.discountType === DiscountTypeEnum.SHIPPING
|
||||||
? VoucherTypeEnum.SHIPPING
|
? VoucherTypeEnum.SHIPPING
|
||||||
: formData.type,
|
: formData.type,
|
||||||
usageLimit: formData.hasUsageLimit
|
usageLimit: formData.hasUsageLimit ? formData.usageLimit : null
|
||||||
? parseInt(formData.usageLimit, 10)
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -56,9 +56,7 @@ export function createUpdateHandler(
|
||||||
formData.discountType === DiscountTypeEnum.SHIPPING
|
formData.discountType === DiscountTypeEnum.SHIPPING
|
||||||
? VoucherTypeEnum.SHIPPING
|
? VoucherTypeEnum.SHIPPING
|
||||||
: formData.type,
|
: formData.type,
|
||||||
usageLimit: formData.hasUsageLimit
|
usageLimit: formData.hasUsageLimit ? formData.usageLimit : null
|
||||||
? parseInt(formData.usageLimit, 10)
|
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
}).then(({ data }) => data?.voucherUpdate.errors ?? []),
|
}).then(({ data }) => data?.voucherUpdate.errors ?? []),
|
||||||
|
|
||||||
|
|
|
@ -99037,7 +99037,7 @@ exports[`Storyshots Views / Discounts / Voucher create default 1`] = `
|
||||||
class="CardTitle-hr-id"
|
class="CardTitle-hr-id"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MuiCardContent-root-id"
|
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root-id"
|
class="MuiFormControlLabel-root-id"
|
||||||
|
@ -100597,7 +100597,7 @@ exports[`Storyshots Views / Discounts / Voucher create form errors 1`] = `
|
||||||
class="CardTitle-hr-id"
|
class="CardTitle-hr-id"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MuiCardContent-root-id"
|
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root-id"
|
class="MuiFormControlLabel-root-id"
|
||||||
|
@ -102790,7 +102790,7 @@ exports[`Storyshots Views / Discounts / Voucher details default 1`] = `
|
||||||
class="CardTitle-hr-id"
|
class="CardTitle-hr-id"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MuiCardContent-root-id"
|
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root-id"
|
class="MuiFormControlLabel-root-id"
|
||||||
|
@ -105165,7 +105165,7 @@ exports[`Storyshots Views / Discounts / Voucher details form errors 1`] = `
|
||||||
class="CardTitle-hr-id"
|
class="CardTitle-hr-id"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MuiCardContent-root-id"
|
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root-id"
|
class="MuiFormControlLabel-root-id"
|
||||||
|
@ -107103,7 +107103,7 @@ exports[`Storyshots Views / Discounts / Voucher details loading 1`] = `
|
||||||
class="CardTitle-hr-id"
|
class="CardTitle-hr-id"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="MuiCardContent-root-id"
|
class="MuiCardContent-root-id VoucherLimits-cardContent-id"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root-id"
|
class="MuiFormControlLabel-root-id"
|
||||||
|
|
Loading…
Reference in a new issue