Enable save button on discount pages (#2319)
* Enable save button on discount pages * Update changelog with save button enable update * Refactor channel input map
This commit is contained in:
parent
781eafa68e
commit
575a91b5e9
10 changed files with 180 additions and 47 deletions
|
@ -12,6 +12,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Fix exit form dialog false positive - #2311 by @orzechdev
|
||||
- Handle form errors before product creation - #2299 by @orzechdev
|
||||
- Fix no product error on unconfirmed order lines - #2324 by @orzechdev
|
||||
- Enable save button on discount pages - #2319 by @orzechdev
|
||||
|
||||
## 3.4
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import {
|
|||
ChannelSaleFormData,
|
||||
SaleDetailsPageFormData,
|
||||
} from "@saleor/discounts/components/SaleDetailsPage";
|
||||
import { VoucherDetailsPageFormData } from "@saleor/discounts/components/VoucherDetailsPage";
|
||||
import { RequirementsPicker } from "@saleor/discounts/types";
|
||||
import {
|
||||
ChannelDetailsFragment,
|
||||
ChannelFragment,
|
||||
|
@ -415,3 +417,11 @@ export const validateSalePrice = (
|
|||
? channel.percentageValue
|
||||
: channel.fixedValue,
|
||||
);
|
||||
|
||||
export const validateVoucherPrice = (
|
||||
data: VoucherDetailsPageFormData,
|
||||
channel: ChannelVoucherData,
|
||||
) =>
|
||||
validatePrice(channel.discountValue) ||
|
||||
(data.requirementsPicker === RequirementsPicker.ORDER &&
|
||||
validatePrice(channel.minSpent));
|
||||
|
|
|
@ -94,7 +94,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
|||
formId={SALE_CREATE_FORM_ID}
|
||||
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
|
||||
>
|
||||
{({ change, data, submit, triggerChange, isSaveDisabled }) => {
|
||||
{({ change, data, submit, triggerChange }) => {
|
||||
const handleChannelChange = createSaleChannelsChangeHandler(
|
||||
data.channelListings,
|
||||
onChannelsChange,
|
||||
|
@ -155,7 +155,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
|||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</Grid>
|
||||
<Savebar
|
||||
disabled={isSaveDisabled}
|
||||
disabled={disabled}
|
||||
onCancel={onBack}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
|
|
|
@ -9,7 +9,10 @@ import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
|
|||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import Savebar from "@saleor/components/Savebar";
|
||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||
import { createSaleChannelsChangeHandler } from "@saleor/discounts/handlers";
|
||||
import {
|
||||
createSaleChannelsChangeHandler,
|
||||
createSaleUpdateHandler,
|
||||
} from "@saleor/discounts/handlers";
|
||||
import { saleListUrl } from "@saleor/discounts/urls";
|
||||
import { SALE_UPDATE_FORM_ID } from "@saleor/discounts/views/SaleDetails/types";
|
||||
import {
|
||||
|
@ -130,6 +133,10 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
|||
const intl = useIntl();
|
||||
const navigate = useNavigator();
|
||||
|
||||
const [localErrors, setLocalErrors] = React.useState<DiscountErrorFragment[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const {
|
||||
makeChangeHandler: makeMetadataChangeHandler,
|
||||
} = useMetadataChangeTrigger();
|
||||
|
@ -159,7 +166,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
|||
formId={SALE_UPDATE_FORM_ID}
|
||||
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
|
||||
>
|
||||
{({ change, data, submit, triggerChange, isSaveDisabled }) => {
|
||||
{({ change, data, submit, triggerChange }) => {
|
||||
const handleChannelChange = createSaleChannelsChangeHandler(
|
||||
data.channelListings,
|
||||
onChannelsChange,
|
||||
|
@ -168,6 +175,10 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
|||
);
|
||||
const changeMetadata = makeMetadataChangeHandler(change);
|
||||
|
||||
const handleSubmit = createSaleUpdateHandler(submit, setLocalErrors);
|
||||
|
||||
const allErrors = [...localErrors, ...errors];
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Backlink href={saleListUrl()}>
|
||||
|
@ -188,7 +199,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
|||
<SaleValue
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
errors={allErrors}
|
||||
onChange={handleChannelChange}
|
||||
/>
|
||||
<CardSpacer />
|
||||
|
@ -346,10 +357,10 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
|||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</Grid>
|
||||
<Savebar
|
||||
disabled={isSaveDisabled}
|
||||
disabled={disabled}
|
||||
onCancel={() => navigate(saleListUrl())}
|
||||
onDelete={onRemove}
|
||||
onSubmit={submit}
|
||||
onSubmit={() => handleSubmit(data)}
|
||||
state={saveButtonBarState}
|
||||
/>
|
||||
</Container>
|
||||
|
|
|
@ -92,6 +92,7 @@ const SaleValue: React.FC<SaleValueProps> = ({
|
|||
const error = formErrors.value?.channels?.find(
|
||||
id => id === listing.id,
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={listing?.id || `skeleton-${index}`}
|
||||
|
@ -109,6 +110,7 @@ const SaleValue: React.FC<SaleValueProps> = ({
|
|||
? getDiscountErrorMessage(formErrors.value, intl)
|
||||
: ""
|
||||
}
|
||||
error={!!error}
|
||||
disabled={disabled}
|
||||
listing={listing}
|
||||
onChange={onChange}
|
||||
|
|
|
@ -10,6 +10,7 @@ interface SaleValueTextFieldProps {
|
|||
dataType: SaleType;
|
||||
helperText: string;
|
||||
disabled: boolean;
|
||||
error: boolean;
|
||||
listing: ChannelSaleFormData;
|
||||
onChange: SaleValueInputOnChangeType;
|
||||
}
|
||||
|
@ -18,6 +19,7 @@ const SaleValueTextField: React.FC<SaleValueTextFieldProps> = ({
|
|||
dataType,
|
||||
helperText,
|
||||
disabled,
|
||||
error,
|
||||
listing,
|
||||
onChange,
|
||||
}) => {
|
||||
|
@ -32,6 +34,7 @@ const SaleValueTextField: React.FC<SaleValueTextFieldProps> = ({
|
|||
<TextField
|
||||
disabled={disabled}
|
||||
helperText={helperText || ""}
|
||||
error={error}
|
||||
name="value"
|
||||
onChange={e => {
|
||||
onChange(id, e.target.value);
|
||||
|
|
|
@ -110,7 +110,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
|||
formId={VOUCHER_CREATE_FORM_ID}
|
||||
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
|
||||
>
|
||||
{({ change, data, submit, triggerChange, set, isSaveDisabled }) => {
|
||||
{({ change, data, submit, triggerChange, set }) => {
|
||||
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
||||
change,
|
||||
);
|
||||
|
@ -203,7 +203,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
|||
<Metadata data={data} onChange={changeMetadata} />
|
||||
</Grid>
|
||||
<Savebar
|
||||
disabled={isSaveDisabled}
|
||||
disabled={disabled}
|
||||
onCancel={() => navigate(voucherListUrl())}
|
||||
onSubmit={submit}
|
||||
state={saveButtonBarState}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { Tab, TabContainer } from "@saleor/components/Tab";
|
|||
import {
|
||||
createChannelsChangeHandler,
|
||||
createDiscountTypeChangeHandler,
|
||||
createVoucherUpdateHandler,
|
||||
} from "@saleor/discounts/handlers";
|
||||
import { DiscountTypeEnum, RequirementsPicker } from "@saleor/discounts/types";
|
||||
import { voucherListUrl } from "@saleor/discounts/urls";
|
||||
|
@ -27,7 +28,6 @@ import {
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||
import { validatePrice } from "@saleor/products/utils/validation";
|
||||
import { mapEdgesToItems, mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||
import React from "react";
|
||||
|
@ -136,6 +136,10 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
|||
const intl = useIntl();
|
||||
const navigate = useNavigator();
|
||||
|
||||
const [localErrors, setLocalErrors] = React.useState<DiscountErrorFragment[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const {
|
||||
makeChangeHandler: makeMetadataChangeHandler,
|
||||
} = useMetadataChangeTrigger();
|
||||
|
@ -192,17 +196,12 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
|||
onChannelsChange,
|
||||
triggerChange,
|
||||
);
|
||||
const formDisabled =
|
||||
(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);
|
||||
|
||||
const handleSubmit = createVoucherUpdateHandler(submit, setLocalErrors);
|
||||
|
||||
const allErrors = [...localErrors, ...errors];
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Backlink href={voucherListUrl()}>
|
||||
|
@ -230,7 +229,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
|||
<VoucherValue
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
errors={errors}
|
||||
errors={allErrors}
|
||||
onChange={change}
|
||||
onChannelChange={handleChannelChange}
|
||||
variant="update"
|
||||
|
@ -415,9 +414,9 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
|||
</Grid>
|
||||
<Savebar
|
||||
onCancel={() => navigate(voucherListUrl())}
|
||||
disabled={disabled || formDisabled}
|
||||
disabled={disabled}
|
||||
onDelete={onRemove}
|
||||
onSubmit={submit}
|
||||
onSubmit={() => handleSubmit(data)}
|
||||
state={saveButtonBarState}
|
||||
/>
|
||||
</Container>
|
||||
|
|
43
src/discounts/data.ts
Normal file
43
src/discounts/data.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { ChannelVoucherData } from "@saleor/channels/utils";
|
||||
import { VoucherChannelListingAddInput } from "@saleor/graphql";
|
||||
|
||||
import { VoucherDetailsPageFormData } from "./components/VoucherDetailsPage";
|
||||
import { RequirementsPicker } from "./types";
|
||||
|
||||
const getChannelDiscountValue = (
|
||||
channel: ChannelVoucherData,
|
||||
formData: VoucherDetailsPageFormData,
|
||||
) =>
|
||||
formData.discountType.toString() === "SHIPPING" ? 100 : channel.discountValue;
|
||||
|
||||
const getChannelMinAmountSpent = (
|
||||
channel: ChannelVoucherData,
|
||||
formData: VoucherDetailsPageFormData,
|
||||
) => {
|
||||
if (formData.requirementsPicker === RequirementsPicker.NONE) {
|
||||
return null;
|
||||
}
|
||||
if (formData.requirementsPicker === RequirementsPicker.ITEM) {
|
||||
return 0;
|
||||
}
|
||||
return channel.minSpent;
|
||||
};
|
||||
|
||||
const mapChannelToChannelInput = (formData: VoucherDetailsPageFormData) => (
|
||||
channel: ChannelVoucherData,
|
||||
) => ({
|
||||
channelId: channel.id,
|
||||
discountValue: getChannelDiscountValue(channel, formData),
|
||||
minAmountSpent: getChannelMinAmountSpent(channel, formData),
|
||||
});
|
||||
|
||||
const filterNotDiscountedChannel = (
|
||||
channelInput: VoucherChannelListingAddInput,
|
||||
) => !!channelInput.discountValue;
|
||||
|
||||
export const getAddedChannelsInputFromFormData = (
|
||||
formData: VoucherDetailsPageFormData,
|
||||
) =>
|
||||
formData.channelListings
|
||||
?.map(mapChannelToChannelInput(formData))
|
||||
.filter(filterNotDiscountedChannel) || [];
|
|
@ -1,15 +1,26 @@
|
|||
import { ChannelVoucherData } from "@saleor/channels/utils";
|
||||
import {
|
||||
ChannelVoucherData,
|
||||
validateSalePrice,
|
||||
validateVoucherPrice,
|
||||
} from "@saleor/channels/utils";
|
||||
import {
|
||||
ChannelSaleFormData,
|
||||
SaleDetailsPageFormData,
|
||||
} from "@saleor/discounts/components/SaleDetailsPage";
|
||||
import { VoucherDetailsPageFormData } from "@saleor/discounts/components/VoucherDetailsPage";
|
||||
import { DiscountTypeEnum, RequirementsPicker } from "@saleor/discounts/types";
|
||||
import { SaleType, VoucherTypeEnum } from "@saleor/graphql";
|
||||
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
|
||||
import { DiscountTypeEnum } from "@saleor/discounts/types";
|
||||
import {
|
||||
DiscountErrorCode,
|
||||
DiscountErrorFragment,
|
||||
SaleType,
|
||||
VoucherTypeEnum,
|
||||
} from "@saleor/graphql";
|
||||
import { ChangeEvent, FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||
import { RequireOnlyOne } from "@saleor/misc";
|
||||
import { arrayDiff } from "@saleor/utils/arrays";
|
||||
|
||||
import { getAddedChannelsInputFromFormData } from "./data";
|
||||
|
||||
export interface ChannelArgs {
|
||||
discountValue: string;
|
||||
minSpent: string;
|
||||
|
@ -116,20 +127,7 @@ export const getChannelsVariables = (
|
|||
return {
|
||||
id,
|
||||
input: {
|
||||
addChannels:
|
||||
formData.channelListings?.map(channel => ({
|
||||
channelId: channel.id,
|
||||
discountValue:
|
||||
formData.discountType.toString() === "SHIPPING"
|
||||
? 100
|
||||
: channel.discountValue,
|
||||
minAmountSpent:
|
||||
formData.requirementsPicker === RequirementsPicker.NONE
|
||||
? null
|
||||
: formData.requirementsPicker === RequirementsPicker.ITEM
|
||||
? 0
|
||||
: channel.minSpent,
|
||||
})) || [],
|
||||
addChannels: getAddedChannelsInputFromFormData(formData),
|
||||
removeChannels: idsDiff.removed,
|
||||
},
|
||||
};
|
||||
|
@ -148,14 +146,80 @@ export const getSaleChannelsVariables = (
|
|||
id,
|
||||
input: {
|
||||
addChannels:
|
||||
formData.channelListings?.map(channel => ({
|
||||
channelId: channel.id,
|
||||
discountValue:
|
||||
formData.type === SaleType.FIXED
|
||||
? channel.fixedValue
|
||||
: channel.percentageValue,
|
||||
})) || [],
|
||||
formData.channelListings
|
||||
?.map(channel => ({
|
||||
channelId: channel.id,
|
||||
discountValue:
|
||||
formData.type === SaleType.FIXED
|
||||
? channel.fixedValue
|
||||
: channel.percentageValue,
|
||||
}))
|
||||
.filter(channel => !!channel.discountValue) || [],
|
||||
removeChannels: idsDiff.removed,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export function createSaleUpdateHandler(
|
||||
submit: (data: SaleDetailsPageFormData) => SubmitPromise<any[]>,
|
||||
setLocalErrors: (errors: DiscountErrorFragment[]) => void,
|
||||
) {
|
||||
return async (formData: SaleDetailsPageFormData) => {
|
||||
const { channelListings } = formData;
|
||||
|
||||
const invalidChannelListings = channelListings
|
||||
?.filter(channel => validateSalePrice(formData, channel))
|
||||
.map(channel => channel.id);
|
||||
const localErrors: DiscountErrorFragment[] = !!invalidChannelListings?.length
|
||||
? [
|
||||
{
|
||||
__typename: "DiscountError",
|
||||
code: DiscountErrorCode.INVALID,
|
||||
field: "value",
|
||||
channels: invalidChannelListings,
|
||||
message: "Invalid discount value",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
setLocalErrors(localErrors);
|
||||
|
||||
if (!!localErrors.length) {
|
||||
return localErrors;
|
||||
}
|
||||
|
||||
return submit(formData);
|
||||
};
|
||||
}
|
||||
|
||||
export function createVoucherUpdateHandler(
|
||||
submit: (data: VoucherDetailsPageFormData) => SubmitPromise<any[]>,
|
||||
setLocalErrors: (errors: DiscountErrorFragment[]) => void,
|
||||
) {
|
||||
return async (formData: VoucherDetailsPageFormData) => {
|
||||
const { channelListings } = formData;
|
||||
|
||||
const invalidChannelListings = channelListings
|
||||
?.filter(channel => validateVoucherPrice(formData, channel))
|
||||
.map(channel => channel.id);
|
||||
const localErrors: DiscountErrorFragment[] = !!invalidChannelListings?.length
|
||||
? [
|
||||
{
|
||||
__typename: "DiscountError",
|
||||
code: DiscountErrorCode.INVALID,
|
||||
field: "discountValue",
|
||||
channels: invalidChannelListings,
|
||||
message: "Invalid discount value",
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
setLocalErrors(localErrors);
|
||||
|
||||
if (!!localErrors.length) {
|
||||
return localErrors;
|
||||
}
|
||||
|
||||
return submit(formData);
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue