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:
Dawid 2022-09-28 10:42:28 +02:00 committed by GitHub
parent 781eafa68e
commit 575a91b5e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 180 additions and 47 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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
View 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) || [];

View file

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