Exit form fixes (#1889)

* Add onBeforeUnload handler to prevent accidental refresh

* Update button messages

* Fix exit form not working after submit

* Make onBeforeUnload disable if env is development

* Fix onClose

* Remove internal date time field state

* Update messages and dialog

* Prevent navigation on 400 error

* Add submit disabled ref in exit form

* Update exit form dialog for disabled save

* Update confirmLeave forms to set ref if save is disabled

* Remove unused error handling

* Remove explicit ref type

* Remove unused import

* Fix disabled type

* Add disable check function to generic forms

* Add custom isDisabled method to sale and voucher forms

* Add default isDisabled functions to confirmLeave forms

* Update tests

* Remove unused code

* Rebase fixes + update tests

* Refactor form and useform

* Refactor disabling forms

* Change "saveDisabled" name to "isSaveDisabled" for improved readability

* Change "isDisabled" function to "checkIfSaveIsDisabled"

* Update exit form disabling conditions for zone rates forms
This commit is contained in:
Wojciech Mista 2022-03-23 10:13:23 +01:00 committed by GitHub
parent 7576afed4e
commit a5ac6bb92e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 717 additions and 390 deletions

View file

@ -2231,19 +2231,23 @@
},
"src_dot_components_dot_Form_dot_cancelButton": {
"context": "ExitFormPrompt cancel button",
"string": "leave without saving"
"string": "Discard changes"
},
"src_dot_components_dot_Form_dot_confirmButton": {
"context": "ExitFormPrompt confirm button",
"string": "save & continue"
"string": "Save changes"
},
"src_dot_components_dot_Form_dot_description": {
"context": "ExitFormPrompt description",
"string": "You have unsaved changes on this view. What would you like to do with them?"
"src_dot_components_dot_Form_dot_continueEditingButton": {
"context": "ExitFormPrompt continue editing button",
"string": "Continue editing"
},
"src_dot_components_dot_Form_dot_title": {
"context": "ExitFormPrompt title",
"string": "Are you sure you want to leave?"
"string": "Would you like to save changes?"
},
"src_dot_components_dot_Form_dot_unableToSaveTitle": {
"context": "ExitFormPrompt title",
"string": "You have unsaved changes"
},
"src_dot_components_dot_ImageUpload_dot_1731007575": {
"context": "image upload",

View file

@ -56,8 +56,13 @@ const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
const permissionsError = getAppErrorMessage(formErrors.permissions, intl);
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ data, change, hasChanged, submit }) => (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, change, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.apps)}
@ -96,7 +101,7 @@ const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
/>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -101,8 +101,13 @@ const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
};
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ data, change, hasChanged, submit }) => (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, change, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.apps)}
@ -182,7 +187,7 @@ const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -156,12 +156,17 @@ const AttributePage: React.FC<AttributePageProps> = ({
};
return (
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
<Form
confirmLeave
initial={initialForm}
onSubmit={handleSubmit}
disabled={disabled}
>
{({
change,
set,
data,
hasChanged,
isSaveDisabled,
submit,
errors,
setError,
@ -239,7 +244,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -31,8 +31,8 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
const intl = useIntl();
return (
<CategoryCreateForm onSubmit={onSubmit}>
{({ data, change, handlers, submit, hasChanged }) => (
<CategoryCreateForm onSubmit={onSubmit} disabled={disabled}>
{({ data, change, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.categories)}
@ -74,7 +74,7 @@ export const CategoryCreatePage: React.FC<CategoryCreatePageProps> = ({
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
/>
</div>
</Container>

View file

@ -33,6 +33,7 @@ export interface UseCategoryCreateFormResult
export interface CategoryCreateFormProps {
children: (props: UseCategoryCreateFormResult) => React.ReactNode;
onSubmit: (data: CategoryCreateData) => Promise<any[]>;
disabled: boolean;
}
const initialData: CategoryCreateFormData = {
@ -45,7 +46,8 @@ const initialData: CategoryCreateFormData = {
};
function useCategoryCreateForm(
onSubmit: (data: CategoryCreateData) => Promise<any[]>
onSubmit: (data: CategoryCreateData) => Promise<any[]>,
disabled: boolean
): UseCategoryCreateFormResult {
const {
handleChange,
@ -53,7 +55,8 @@ function useCategoryCreateForm(
hasChanged,
triggerChange,
setChanged,
formId
formId,
setIsSubmitDisabled
} = useForm(initialData, undefined, { confirmLeave: true });
const handleFormSubmit = useHandleFormSubmit({
@ -87,6 +90,9 @@ function useCategoryCreateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const isSaveDisabled = disabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data: getData(),
@ -95,15 +101,17 @@ function useCategoryCreateForm(
changeMetadata
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
const CategoryCreateForm: React.FC<CategoryCreateFormProps> = ({
children,
onSubmit
onSubmit,
disabled
}) => {
const props = useCategoryCreateForm(onSubmit);
const props = useCategoryCreateForm(onSubmit, disabled);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -92,8 +92,12 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
const intl = useIntl();
return (
<CategoryUpdateForm category={category} onSubmit={onSubmit}>
{({ data, change, handlers, submit, hasChanged }) => (
<CategoryUpdateForm
category={category}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, change, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.categories)}
@ -217,7 +221,7 @@ export const CategoryUpdatePage: React.FC<CategoryUpdatePageProps> = ({
onDelete={onDelete}
onSubmit={submit}
state={saveButtonBarState}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
/>
</Container>
)}

View file

@ -38,6 +38,7 @@ export interface CategoryUpdateFormProps {
children: (props: UseCategoryUpdateFormResult) => React.ReactNode;
category: CategoryDetailsFragment;
onSubmit: (data: CategoryUpdateData) => Promise<any[]>;
disabled: boolean;
}
const getInitialData = (category?: CategoryDetailsFragment) => ({
@ -52,7 +53,8 @@ const getInitialData = (category?: CategoryDetailsFragment) => ({
function useCategoryUpdateForm(
category: CategoryDetailsFragment,
onSubmit: (data: CategoryUpdateData) => Promise<any[]>
onSubmit: (data: CategoryUpdateData) => Promise<any[]>,
disabled: boolean
): UseCategoryUpdateFormResult {
const {
handleChange,
@ -60,7 +62,8 @@ function useCategoryUpdateForm(
triggerChange,
hasChanged,
setChanged,
formId
formId,
setIsSubmitDisabled
} = useForm(getInitialData(category), undefined, { confirmLeave: true });
const handleFormSubmit = useHandleFormSubmit({
@ -101,6 +104,9 @@ function useCategoryUpdateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const isSaveDisabled = disabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data: getData(),
@ -109,16 +115,18 @@ function useCategoryUpdateForm(
changeMetadata
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
const CategoryUpdateForm: React.FC<CategoryUpdateFormProps> = ({
children,
category,
onSubmit
onSubmit,
disabled
}) => {
const props = useCategoryUpdateForm(category, onSubmit);
const props = useCategoryUpdateForm(category, onSubmit, disabled);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -1,6 +1,6 @@
import ShippingZonesCard from "@saleor/channels/components/ShippingZonesCard/ShippingZonesCard";
import CardSpacer from "@saleor/components/CardSpacer";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
import Savebar from "@saleor/components/Savebar";
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
@ -94,9 +94,25 @@ const ChannelDetailsPage = function<TErrors>({
!shippingZonesToDisplay.some(({ id }) => id === searchedZoneId)
);
const checkIfSaveIsDisabled = (data: FormDataWithOpts<FormData>) => {
const formDisabled =
!data.name ||
!data.slug ||
!data.currencyCode ||
!data.defaultCountry ||
!(data.name.trim().length > 0);
return disabled || formDisabled || !data.hasChanged;
};
return (
<Form confirmLeave onSubmit={onSubmit} initial={initialData}>
{({ change, data, hasChanged, submit, set }) => {
<Form
confirmLeave
onSubmit={onSubmit}
initial={initialData}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, submit, set, isSaveDisabled }) => {
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
change,
setSelectedCurrencyCode,
@ -147,13 +163,6 @@ const ChannelDetailsPage = function<TErrors>({
);
};
const formDisabled =
!data.name ||
!data.slug ||
!data.currencyCode ||
!data.defaultCountry ||
!(data.name.trim().length > 0);
return (
<>
<Grid>
@ -197,7 +206,7 @@ const ChannelDetailsPage = function<TErrors>({
onSubmit={submit}
onDelete={onDelete}
state={saveButtonBarState}
disabled={disabled || formDisabled || !onSubmit || !hasChanged}
disabled={isSaveDisabled}
/>
</>
);

View file

@ -54,8 +54,9 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
onSubmit={onSubmit}
currentChannels={currentChannels}
setChannels={onChannelsChange}
disabled={disabled}
>
{({ change, data, handlers, hasChanged, submit }) => (
{({ change, data, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.collections)}
@ -156,7 +157,7 @@ const CollectionCreatePage: React.FC<CollectionCreatePageProps> = ({
</Grid>
<Savebar
state={saveButtonBarState}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onSubmit={submit}
/>

View file

@ -49,6 +49,7 @@ export interface CollectionCreateFormProps {
setChannels: (data: ChannelCollectionData[]) => void;
children: (props: UseCollectionCreateFormResult) => React.ReactNode;
onSubmit: (data: CollectionCreateData) => SubmitPromise;
disabled: boolean;
}
const getInitialData = (
@ -71,7 +72,8 @@ const getInitialData = (
function useCollectionCreateForm(
currentChannels: ChannelCollectionData[],
setChannels: (data: ChannelCollectionData[]) => void,
onSubmit: (data: CollectionCreateData) => SubmitPromise
onSubmit: (data: CollectionCreateData) => SubmitPromise,
disabled: boolean
): UseCollectionCreateFormResult {
const {
handleChange,
@ -79,7 +81,8 @@ function useCollectionCreateForm(
triggerChange,
setChanged,
hasChanged,
formId
formId,
setIsSubmitDisabled
} = useForm(getInitialData(currentChannels), undefined, {
confirmLeave: true,
formId: COLLECTION_CREATE_FORM_ID
@ -122,6 +125,9 @@ function useCollectionCreateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const isSaveDisabled = disabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data: getData(),
@ -131,7 +137,8 @@ function useCollectionCreateForm(
changeMetadata
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -139,9 +146,15 @@ const CollectionCreateForm: React.FC<CollectionCreateFormProps> = ({
currentChannels,
setChannels,
children,
onSubmit
onSubmit,
disabled
}) => {
const props = useCollectionCreateForm(currentChannels, setChannels, onSubmit);
const props = useCollectionCreateForm(
currentChannels,
setChannels,
onSubmit,
disabled
);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -73,8 +73,10 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
currentChannels={currentChannels}
setChannels={onChannelsChange}
onSubmit={onSubmit}
disabled={disabled}
hasChannelChanged={hasChannelChanged}
>
{({ change, data, handlers, hasChanged, submit }) => (
{({ change, data, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.collections)}
@ -150,7 +152,7 @@ const CollectionDetailsPage: React.FC<CollectionDetailsPageProps> = ({
</Grid>
<Savebar
state={saveButtonBarState}
disabled={disabled || (!hasChanged && !hasChannelChanged)}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onCollectionRemove}
onSubmit={submit}

View file

@ -48,6 +48,8 @@ export interface CollectionUpdateFormProps {
currentChannels: ChannelCollectionData[];
setChannels: (data: ChannelCollectionData[]) => void;
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
disabled: boolean;
hasChannelChanged: boolean;
}
const getInitialData = (
@ -68,7 +70,9 @@ function useCollectionUpdateForm(
collection: CollectionDetailsFragment,
currentChannels: ChannelCollectionData[],
setChannels: (data: ChannelCollectionData[]) => void,
onSubmit: (data: CollectionUpdateData) => Promise<any[]>
onSubmit: (data: CollectionUpdateData) => Promise<any[]>,
disabled: boolean,
hasChannelChanged: boolean
): UseCollectionUpdateFormResult {
const {
handleChange,
@ -76,7 +80,8 @@ function useCollectionUpdateForm(
triggerChange,
setChanged,
hasChanged,
formId
formId,
setIsSubmitDisabled
} = useForm(getInitialData(collection, currentChannels), undefined, {
confirmLeave: true,
formId: COLLECTION_DETAILS_FORM_ID
@ -126,6 +131,9 @@ function useCollectionUpdateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const isSaveDisabled = disabled || (!hasChanged && !hasChannelChanged);
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data: getData(),
@ -135,7 +143,8 @@ function useCollectionUpdateForm(
changeMetadata
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -144,13 +153,17 @@ const CollectionUpdateForm: React.FC<CollectionUpdateFormProps> = ({
currentChannels,
setChannels,
children,
onSubmit
onSubmit,
disabled,
hasChannelChanged
}) => {
const props = useCollectionUpdateForm(
collection,
currentChannels,
setChannels,
onSubmit
onSubmit,
disabled,
hasChannelChanged
);
return <form onSubmit={props.submit}>{children(props)}</form>;

View file

@ -6,8 +6,8 @@ import {
ProductErrorWithAttributesFragment
} from "@saleor/graphql";
import { commonMessages } from "@saleor/intl";
import { DateTime, joinDateTime, splitDateTime } from "@saleor/misc";
import React, { useEffect, useState } from "react";
import { joinDateTime, splitDateTime } from "@saleor/misc";
import React from "react";
import { useIntl } from "react-intl";
type DateTimeFieldProps = Omit<TextFieldProps, "label" | "error"> & {
@ -21,14 +21,11 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
error,
name,
onChange,
value: initialValue
value
}) => {
const intl = useIntl();
const [value, setValue] = useState<DateTime>(
initialValue ? splitDateTime(initialValue) : { date: "", time: "" }
);
useEffect(() => onChange(joinDateTime(value.date, value.time)), [value]);
const parsedValue = value ? splitDateTime(value) : { date: "", time: "" };
return (
<>
@ -41,10 +38,11 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
name={`${name}:date`}
onChange={event => {
const date = event.target.value;
setValue(value => ({ ...value, date }));
onChange(joinDateTime(date, parsedValue.time));
}}
type="date"
value={value.date}
value={parsedValue.date}
InputLabelProps={{ shrink: true }}
/>
<TextField
@ -56,10 +54,11 @@ export const DateTimeField: React.FC<DateTimeFieldProps> = ({
name={`${name}:time`}
onChange={event => {
const time = event.target.value;
setValue(value => ({ ...value, time }));
onChange(joinDateTime(parsedValue.date, time));
}}
type="time"
value={value.time}
value={parsedValue.time}
InputLabelProps={{ shrink: true }}
/>
</>

View file

@ -1,9 +1,8 @@
import { Button, Dialog, DialogContent, makeStyles } from "@material-ui/core";
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useIntl } from "react-intl";
import { exitFormPromptMessages as messages } from "./messages";
@ -19,6 +18,12 @@ const useStyles = makeStyles(
buttonsContainer: {
display: "flex",
justifyContent: "flex-end"
},
dialogContent: {
"@media (min-width: 800px)": {
minWidth: 500
},
paddingTop: 0
}
}),
{ name: "ExitFormPrompt" }
@ -29,24 +34,27 @@ interface ExitFormDialogProps {
onClose: () => void;
onLeave: () => void;
isOpen: boolean;
isSubmitDisabled: boolean;
}
const ExitFormDialog: React.FC<ExitFormDialogProps> = ({
onSubmit,
onLeave,
onClose,
isOpen
isOpen,
isSubmitDisabled
}) => {
const classes = useStyles();
const intl = useIntl();
return (
<Dialog className={classes.container} open={isOpen}>
<CardTitle title={intl.formatMessage(messages.title)} onClose={onClose} />
<DialogContent>
<FormattedMessage {...messages.description} />
<CardSpacer />
<CardSpacer />
<Dialog className={classes.container} open={isOpen} onClose={onClose}>
<CardTitle
title={intl.formatMessage(
isSubmitDisabled ? messages.unableToSaveTitle : messages.title
)}
/>
<DialogContent className={classes.dialogContent}>
<div className={classes.buttonsContainer}>
<Button onClick={onLeave}>
{intl.formatMessage(messages.cancelButton)}
@ -55,10 +63,14 @@ const ExitFormDialog: React.FC<ExitFormDialogProps> = ({
<Button
variant="contained"
color="primary"
onClick={onSubmit}
onClick={isSubmitDisabled ? onClose : onSubmit}
data-test-id="save-and-continue"
>
{intl.formatMessage(messages.confirmButton)}
{intl.formatMessage(
isSubmitDisabled
? messages.continueEditingButton
: messages.confirmButton
)}
</Button>
</div>
</DialogContent>

View file

@ -1,9 +1,11 @@
import { SubmitPromise } from "@saleor/hooks/useForm";
import { isInDevelopment } from "@saleor/misc";
import React, { useEffect, useRef, useState } from "react";
import { useHistory } from "react-router";
import useRouter from "use-react-router";
import ExitFormDialog from "./ExitFormDialog";
import useBeforeUnload from "./useBeforeUnload";
export interface ExitFormDialogData {
setIsDirty: (id: symbol, isDirty: boolean) => void;
@ -12,6 +14,7 @@ export interface ExitFormDialogData {
shouldBlockNavigation: () => boolean;
setIsSubmitting: (value: boolean) => void;
submit: () => SubmitPromise;
setIsSubmitDisabled: (value: boolean) => void;
}
export type SubmitFn = (dataOrEvent?: any) => SubmitPromise<any[]>;
@ -37,7 +40,8 @@ export const ExitFormDialogContext = React.createContext<ExitFormDialogData>({
setExitDialogSubmitRef: () => undefined,
shouldBlockNavigation: () => false,
setIsSubmitting: () => undefined,
submit: () => Promise.resolve([])
submit: () => Promise.resolve([]),
setIsSubmitDisabled: () => undefined
});
const defaultValues = {
@ -56,6 +60,11 @@ export function useExitFormDialogProvider() {
const { history: routerHistory } = useRouter();
const [showDialog, setShowDialog] = useState(defaultValues.showDialog);
const isSubmitDisabled = useRef(false);
const setIsSubmitDisabled = (status: boolean) => {
isSubmitDisabled.current = status;
};
const isSubmitting = useRef(defaultValues.isSubmitting);
const formsData = useRef<FormsData>({});
@ -209,10 +218,11 @@ export function useExitFormDialogProvider() {
const errors = await Promise.all(
getDirtyFormsSubmitFn().map(submitFn => submitFn())
);
const isError = errors.flat().some(errors => errors);
setIsSubmitting(false);
const isError = errors.flat().some(errors => errors);
if (!isError) {
continueNavigation();
}
@ -239,7 +249,8 @@ export function useExitFormDialogProvider() {
setEnableExitDialog,
setExitDialogSubmitRef: setSubmitRef,
setIsSubmitting,
submit: handleSubmit
submit: handleSubmit,
setIsSubmitDisabled
};
return {
@ -247,7 +258,9 @@ export function useExitFormDialogProvider() {
showDialog,
handleSubmit,
handleLeave,
handleClose
handleClose,
shouldBlockNav,
isSubmitDisabled
};
}
@ -257,9 +270,20 @@ const ExitFormDialogProvider = ({ children }) => {
handleLeave,
handleSubmit,
providerData,
showDialog
showDialog,
shouldBlockNav,
isSubmitDisabled
} = useExitFormDialogProvider();
useBeforeUnload(e => {
// If form is dirty and user does a refresh,
// the browser will ask about unsaved changes
if (shouldBlockNav() && !isInDevelopment) {
e.preventDefault();
e.returnValue = "";
}
});
return (
<ExitFormDialogContext.Provider value={providerData}>
<ExitFormDialog
@ -267,6 +291,7 @@ const ExitFormDialogProvider = ({ children }) => {
onSubmit={handleSubmit}
onLeave={handleLeave}
onClose={handleClose}
isSubmitDisabled={isSubmitDisabled.current}
/>
{children}
</ExitFormDialogContext.Provider>

View file

@ -3,6 +3,13 @@ import React from "react";
import { FormId } from "./ExitFormDialogProvider";
export type FormDataWithOpts<TData> = TData &
Pick<UseFormResult<TData>, "hasChanged">;
export type CheckIfSaveIsDisabledFnType<T> = (
data: FormDataWithOpts<T>
) => boolean;
export interface FormProps<TData, TErrors>
extends Omit<React.HTMLProps<HTMLFormElement>, "onSubmit"> {
children: (props: UseFormResult<TData>) => React.ReactNode;
@ -11,6 +18,7 @@ export interface FormProps<TData, TErrors>
resetOnSubmit?: boolean;
onSubmit?: (data: TData) => SubmitPromise<TErrors[]> | void;
formId?: FormId;
checkIfSaveIsDisabled?: CheckIfSaveIsDisabledFnType<TData>;
}
function Form<TData, Terrors>({
@ -20,9 +28,16 @@ function Form<TData, Terrors>({
onSubmit,
confirmLeave = false,
formId,
checkIfSaveIsDisabled,
disabled,
...rest
}: FormProps<TData, Terrors>) {
const renderProps = useForm(initial, onSubmit, { confirmLeave, formId });
const renderProps = useForm(initial, onSubmit, {
confirmLeave,
formId,
checkIfSaveIsDisabled,
disabled
});
function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) {
const { reset, submit } = renderProps;

View file

@ -2,20 +2,23 @@ import { defineMessages } from "react-intl";
export const exitFormPromptMessages = defineMessages({
title: {
defaultMessage: "Are you sure you want to leave?",
defaultMessage: "Would you like to save changes?",
description: "ExitFormPrompt title"
},
description: {
defaultMessage:
"You have unsaved changes on this view. What would you like to do with them?",
description: "ExitFormPrompt description"
unableToSaveTitle: {
defaultMessage: "You have unsaved changes",
description: "ExitFormPrompt title"
},
cancelButton: {
defaultMessage: "leave without saving",
defaultMessage: "Discard changes",
description: "ExitFormPrompt cancel button"
},
confirmButton: {
defaultMessage: "save & continue",
defaultMessage: "Save changes",
description: "ExitFormPrompt confirm button"
},
continueEditingButton: {
defaultMessage: "Continue editing",
description: "ExitFormPrompt continue editing button"
}
});

View file

@ -0,0 +1,19 @@
import { useEffect, useRef } from "react";
const useBeforeUnload = fn => {
const cb = useRef(fn);
useEffect(() => {
cb.current = fn;
}, [fn]);
useEffect(() => {
const onBeforeUnload = (...args) => cb.current?.(...args);
window.addEventListener("beforeunload", onBeforeUnload);
return () => window.removeEventListener("beforeunload", onBeforeUnload);
}, []);
};
export default useBeforeUnload;

View file

@ -1,4 +1,4 @@
import { useContext, useRef } from "react";
import React, { useContext, useRef } from "react";
import {
ExitFormDialogContext,
@ -8,7 +8,14 @@ import {
} from "./ExitFormDialogProvider";
export interface UseExitFormDialogResult
extends Omit<ExitFormDialogData, "setIsDirty" | "setExitDialogSubmitRef">,
extends Pick<
ExitFormDialogData,
| "setEnableExitDialog"
| "shouldBlockNavigation"
| "setIsSubmitting"
| "setIsSubmitDisabled"
| "submit"
>,
WithFormId {
setIsDirty: (isDirty: boolean) => void;
setExitDialogSubmitRef: (submitFn: SubmitFn) => void;
@ -16,19 +23,29 @@ export interface UseExitFormDialogResult
export interface UseExitFormDialogProps {
formId: symbol;
isDisabled?: boolean;
}
export const useExitFormDialog = (
{ formId }: UseExitFormDialogProps = { formId: undefined }
{ formId, isDisabled }: UseExitFormDialogProps = { formId: undefined }
): UseExitFormDialogResult => {
const id = useRef(formId || Symbol()).current;
const { setIsDirty, setExitDialogSubmitRef, ...rest } = useContext(
ExitFormDialogContext
);
const exitDialogProps = useContext(ExitFormDialogContext);
const {
setIsDirty,
setIsSubmitDisabled,
setExitDialogSubmitRef
} = exitDialogProps;
React.useEffect(() => {
if (isDisabled !== undefined) {
setIsSubmitDisabled(isDisabled);
}
}, [isDisabled]);
return {
...rest,
...exitDialogProps,
formId: id,
setIsDirty: (value: boolean) => setIsDirty(id, value),
setExitDialogSubmitRef: (submitFn: SubmitFn) =>

View file

@ -135,8 +135,13 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
};
return (
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
{({ change, data, hasChanged, submit }) => {
<Form
confirmLeave
initial={initialForm}
onSubmit={handleSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => {
const handleCountrySelect = createSingleAutocompleteSelectHandler(
change,
setCountryDisplayName,
@ -182,7 +187,7 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBar}
onSubmit={submit}
onCancel={onBack}

View file

@ -80,8 +80,13 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
} = useMetadataChangeTrigger();
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => {
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => {
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -136,7 +141,7 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBar}
onSubmit={submit}
onCancel={onBack}

View file

@ -2,7 +2,7 @@ import { validateSalePrice } from "@saleor/channels/utils";
import CardSpacer from "@saleor/components/CardSpacer";
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader";
@ -81,23 +81,26 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
privateMetadata: []
};
const checkIfSaveIsDisabled = (data: FormDataWithOpts<FormData>) =>
data.channelListings?.some(channel => validateSalePrice(data, channel)) ||
disabled ||
!data.hasChanged;
return (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
formId={SALE_CREATE_FORM_ID}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, hasChanged, submit, triggerChange }) => {
{({ change, data, submit, triggerChange, isSaveDisabled }) => {
const handleChannelChange = createSaleChannelsChangeHandler(
data.channelListings,
onChannelsChange,
triggerChange,
data.type
);
const formDisabled = data.channelListings?.some(channel =>
validateSalePrice(data, channel)
);
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -152,7 +155,7 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
<Metadata data={data} onChange={changeMetadata} />
</Grid>
<Savebar
disabled={disabled || formDisabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}

View file

@ -2,7 +2,7 @@ import { ChannelSaleData, validateSalePrice } from "@saleor/channels/utils";
import CardSpacer from "@saleor/components/CardSpacer";
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader";
@ -156,23 +156,29 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
metadata: sale?.metadata.map(mapMetadataItemToInput),
privateMetadata: sale?.privateMetadata.map(mapMetadataItemToInput)
};
const checkIfSaveIsDisabled = (
data: FormDataWithOpts<SaleDetailsPageFormData>
) =>
data.channelListings?.some(channel => validateSalePrice(data, channel)) ||
disabled ||
(!data.hasChanged && !hasChannelChanged);
return (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
formId={SALE_UPDATE_FORM_ID}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, hasChanged, submit, triggerChange }) => {
{({ change, data, submit, triggerChange, isSaveDisabled }) => {
const handleChannelChange = createSaleChannelsChangeHandler(
data.channelListings,
onChannelsChange,
triggerChange,
data.type
);
const formDisabled = data.channelListings?.some(channel =>
validateSalePrice(data, channel)
);
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -370,9 +376,7 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
<Metadata data={data} onChange={changeMetadata} />
</Grid>
<Savebar
disabled={
disabled || formDisabled || (!hasChanged && !hasChannelChanged)
}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onRemove}
onSubmit={submit}

View file

@ -2,7 +2,7 @@ import { ChannelVoucherData } from "@saleor/channels/utils";
import CardSpacer from "@saleor/components/CardSpacer";
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
import Metadata from "@saleor/components/Metadata";
import PageHeader from "@saleor/components/PageHeader";
@ -91,14 +91,26 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
privateMetadata: []
};
const checkIfSaveIsDisabled = (data: FormDataWithOpts<FormData>) =>
(data.discountType.toString() !== "SHIPPING" &&
data.channelListings?.some(
channel =>
validatePrice(channel.discountValue) ||
(data.requirementsPicker === RequirementsPicker.ORDER &&
validatePrice(channel.minSpent))
)) ||
disabled ||
(!data.hasChanged && hasChannelChanged);
return (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
formId={VOUCHER_CREATE_FORM_ID}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, hasChanged, submit, triggerChange, set }) => {
{({ change, data, submit, triggerChange, set, isSaveDisabled }) => {
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
change
);
@ -107,14 +119,6 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
onChannelsChange,
triggerChange
);
const formDisabled =
data.discountType.toString() !== "SHIPPING" &&
data.channelListings?.some(
channel =>
validatePrice(channel.discountValue) ||
(data.requirementsPicker === RequirementsPicker.ORDER &&
validatePrice(channel.minSpent))
);
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -199,9 +203,7 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
<Metadata data={data} onChange={changeMetadata} />
</Grid>
<Savebar
disabled={
disabled || formDisabled || (!hasChanged && !hasChannelChanged)
}
disabled={isSaveDisabled}
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}

View file

@ -1,3 +1,4 @@
import { CheckIfSaveIsDisabledFnType } from "@saleor/components/Form";
import { FormId } from "@saleor/components/Form/ExitFormDialogProvider";
import {
useExitFormDialog,
@ -25,9 +26,11 @@ export type FormErrors<T> = {
[field in keyof T]?: string | React.ReactNode;
};
export interface UseFormOpts {
export interface UseFormOpts<T> {
confirmLeave: boolean;
formId?: FormId;
checkIfSaveIsDisabled?: CheckIfSaveIsDisabledFnType<T>;
disabled?: boolean;
}
export interface UseFormResult<TData>
@ -42,6 +45,7 @@ export interface UseFormResult<TData>
errors: FormErrors<TData>;
setError: (name: keyof TData, error: string | React.ReactNode) => void;
clearErrors: (name?: keyof TData | Array<keyof TData>) => void;
setIsSubmitDisabled: (value: boolean) => void;
}
export interface CommonUseFormResult<TData> {
@ -49,6 +53,7 @@ export interface CommonUseFormResult<TData> {
change: FormChange;
hasChanged: boolean;
submit: (dataOrEvent?: any) => SubmitPromise<any[]>;
isSaveDisabled?: boolean;
}
export interface CommonUseFormResultWithHandlers<TData, THandlers>
@ -84,9 +89,14 @@ function handleRefresh<T extends FormData>(
function useForm<T extends FormData, TErrors>(
initialData: T,
onSubmit?: (data: T) => SubmitPromise<TErrors[]> | void,
opts: UseFormOpts = { confirmLeave: false, formId: undefined }
opts: UseFormOpts<T> = { confirmLeave: false, formId: undefined }
): UseFormResult<T> {
const { confirmLeave, formId: propsFormId } = opts;
const {
confirmLeave,
formId: propsFormId,
checkIfSaveIsDisabled,
disabled
} = opts;
const [hasChanged, setChanged] = useState(false);
const [errors, setErrors] = useState<FormErrors<T>>({});
const [data, setData] = useStateFromProps(initialData, {
@ -94,12 +104,33 @@ function useForm<T extends FormData, TErrors>(
onRefresh: newData => handleRefresh(data, newData, handleSetChanged)
});
const basicFormDisableConditions = () => !hasChanged || disabled;
const isSaveDisabled = () => {
if (checkIfSaveIsDisabled) {
return checkIfSaveIsDisabled({
...data,
hasChanged
});
}
if (disabled !== undefined) {
return basicFormDisableConditions();
}
return false;
};
const {
setIsDirty: setIsFormDirtyInExitDialog,
setExitDialogSubmitRef,
setEnableExitDialog,
setIsSubmitDisabled,
formId
} = useExitFormDialog({ formId: propsFormId });
} = useExitFormDialog({
formId: propsFormId,
isDisabled: isSaveDisabled()
});
const handleFormSubmit = useHandleFormSubmit({
formId,
@ -216,7 +247,9 @@ function useForm<T extends FormData, TErrors>(
toggleValue,
handleChange,
triggerChange: handleSetChanged,
setChanged: handleSetChanged
setChanged: handleSetChanged,
setIsSubmitDisabled,
isSaveDisabled: isSaveDisabled()
};
}

View file

@ -35,14 +35,14 @@ function useHandleFormSubmit<TData, TErrors>({
const errors = await result;
setIsSubmitting(false);
if (errors?.length === 0) {
setChanged(false);
return [];
}
setIsSubmitting(false);
return errors;
}

View file

@ -522,3 +522,6 @@ export const combinedMultiAutocompleteChoices = (
selected: MultiAutocompleteChoiceType[],
choices: MultiAutocompleteChoiceType[]
) => uniqBy([...selected, ...choices], "value");
export const isInDevelopment =
!process.env.NODE_ENV || process.env.NODE_ENV === "development";

View file

@ -67,8 +67,9 @@ const OrderRefundPage: React.FC<OrderRefundPageProps> = props => {
order={order}
defaultType={defaultType}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, handlers, change, submit }) => {
{({ data, handlers, change, submit, isSaveDisabled }) => {
const isProductRefund = data.type === OrderRefundType.PRODUCTS;
return (
@ -156,7 +157,7 @@ const OrderRefundPage: React.FC<OrderRefundPageProps> = props => {
}
data={data}
order={order}
disabled={disabled}
disabled={isSaveDisabled}
errors={errors}
onChange={change}
onRefund={submit}

View file

@ -57,6 +57,7 @@ interface OrderRefundFormProps {
order: OrderRefundDataQuery["order"];
defaultType: OrderRefundType;
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise;
disabled: boolean;
}
function getOrderRefundPageFormData(
@ -73,7 +74,8 @@ function getOrderRefundPageFormData(
function useOrderRefundForm(
order: OrderRefundDataQuery["order"],
defaultType: OrderRefundType,
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise,
disabled: boolean
): UseOrderRefundFormResult {
const {
handleChange,
@ -81,12 +83,15 @@ function useOrderRefundForm(
hasChanged,
triggerChange,
data: formData,
formId
formId,
setIsSubmitDisabled
} = useForm(getOrderRefundPageFormData(defaultType), undefined, {
confirmLeave: true
});
const { setExitDialogSubmitRef } = useExitFormDialog();
const { setExitDialogSubmitRef } = useExitFormDialog({
formId
});
const refundedProductQuantities = useFormset<null, string>(
order?.lines
@ -188,7 +193,8 @@ function useOrderRefundForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const disabled = !order;
const isSaveDisabled = disabled || !order;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
@ -201,7 +207,8 @@ function useOrderRefundForm(
setMaximalRefundedProductQuantities: handleMaximalRefundedProductQuantitiesSet
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -209,9 +216,10 @@ const OrderRefundForm: React.FC<OrderRefundFormProps> = ({
children,
order,
defaultType,
onSubmit
onSubmit,
disabled
}) => {
const props = useOrderRefundForm(order, defaultType, onSubmit);
const props = useOrderRefundForm(order, defaultType, onSubmit, disabled);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -45,110 +45,97 @@ const OrderRefundPage: React.FC<OrderReturnPageProps> = props => {
const intl = useIntl();
return (
<OrderRefundForm order={order} onSubmit={onSubmit}>
{({ data, handlers, change, submit }) => {
const {
fulfilledItemsQuantities,
waitingItemsQuantities,
unfulfilledItemsQuantities
} = data;
const hasAnyItemsSelected =
fulfilledItemsQuantities.some(({ value }) => !!value) ||
waitingItemsQuantities.some(({ value }) => !!value) ||
unfulfilledItemsQuantities.some(({ value }) => !!value);
return (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(messages.appTitle, {
orderNumber: order?.number
})}
</Backlink>
<PageHeader
title={intl.formatMessage(messages.pageTitle, {
orderNumber: order?.number
})}
/>
<Grid>
<div>
{!!data.unfulfilledItemsQuantities.length && (
<>
{({ data, handlers, change, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(messages.appTitle, {
orderNumber: order?.number
})}
</Backlink>
<PageHeader
title={intl.formatMessage(messages.pageTitle, {
orderNumber: order?.number
})}
/>
<Grid>
<div>
{!!data.unfulfilledItemsQuantities.length && (
<>
<ItemsCard
errors={errors}
order={order}
lines={getUnfulfilledLines(order)}
itemsQuantities={data.unfulfilledItemsQuantities}
itemsSelections={data.itemsToBeReplaced}
onChangeQuantity={handlers.changeUnfulfiledItemsQuantity}
onSetMaxQuantity={
handlers.handleSetMaximalUnfulfiledItemsQuantities
}
onChangeSelected={handlers.changeItemsToBeReplaced}
/>
<CardSpacer />
</>
)}
{renderCollection(
getWaitingFulfillments(order),
({ id, lines }) => (
<React.Fragment key={id}>
<ItemsCard
errors={errors}
order={order}
lines={getUnfulfilledLines(order)}
itemsQuantities={data.unfulfilledItemsQuantities}
fulfilmentId={id}
lines={getParsedLines(lines)}
itemsQuantities={data.waitingItemsQuantities}
itemsSelections={data.itemsToBeReplaced}
onChangeQuantity={handlers.changeUnfulfiledItemsQuantity}
onSetMaxQuantity={
handlers.handleSetMaximalUnfulfiledItemsQuantities
}
onChangeQuantity={handlers.changeWaitingItemsQuantity}
onSetMaxQuantity={handlers.handleSetMaximalItemsQuantities(
id
)}
onChangeSelected={handlers.changeItemsToBeReplaced}
/>
<CardSpacer />
</>
)}
{renderCollection(
getWaitingFulfillments(order),
({ id, lines }) => (
<React.Fragment key={id}>
<ItemsCard
errors={errors}
order={order}
fulfilmentId={id}
lines={getParsedLines(lines)}
itemsQuantities={data.waitingItemsQuantities}
itemsSelections={data.itemsToBeReplaced}
onChangeQuantity={handlers.changeWaitingItemsQuantity}
onSetMaxQuantity={handlers.handleSetMaximalItemsQuantities(
id
)}
onChangeSelected={handlers.changeItemsToBeReplaced}
/>
<CardSpacer />
</React.Fragment>
)
)}
{renderCollection(
getFulfilledFulfillemnts(order),
({ id, lines }) => (
<React.Fragment key={id}>
<ItemsCard
errors={errors}
order={order}
fulfilmentId={id}
lines={getParsedLines(lines)}
itemsQuantities={data.fulfilledItemsQuantities}
itemsSelections={data.itemsToBeReplaced}
onChangeQuantity={handlers.changeFulfiledItemsQuantity}
onSetMaxQuantity={handlers.handleSetMaximalItemsQuantities(
id
)}
onChangeSelected={handlers.changeItemsToBeReplaced}
/>
<CardSpacer />
</React.Fragment>
)
)}
</div>
<div>
<OrderAmount
allowNoRefund
isReturn
amountData={getReturnProductsAmountValues(order, data)}
data={data}
order={order}
disableSubmitButton={!hasAnyItemsSelected}
disabled={loading}
errors={errors}
onChange={change}
onRefund={submit}
/>
</div>
</Grid>
</Container>
);
}}
</React.Fragment>
)
)}
{renderCollection(
getFulfilledFulfillemnts(order),
({ id, lines }) => (
<React.Fragment key={id}>
<ItemsCard
errors={errors}
order={order}
fulfilmentId={id}
lines={getParsedLines(lines)}
itemsQuantities={data.fulfilledItemsQuantities}
itemsSelections={data.itemsToBeReplaced}
onChangeQuantity={handlers.changeFulfiledItemsQuantity}
onSetMaxQuantity={handlers.handleSetMaximalItemsQuantities(
id
)}
onChangeSelected={handlers.changeItemsToBeReplaced}
/>
<CardSpacer />
</React.Fragment>
)
)}
</div>
<div>
<OrderAmount
allowNoRefund
isReturn
amountData={getReturnProductsAmountValues(order, data)}
data={data}
order={order}
disableSubmitButton={isSaveDisabled}
disabled={loading}
errors={errors}
onChange={change}
onRefund={submit}
/>
</div>
</Grid>
</Container>
)}
</OrderRefundForm>
);
};

View file

@ -85,12 +85,15 @@ function useOrderReturnForm(
hasChanged,
data: formData,
triggerChange,
formId
formId,
setIsSubmitDisabled
} = useForm(getOrderRefundPageFormData(), undefined, {
confirmLeave: true
});
const { setExitDialogSubmitRef } = useExitFormDialog();
const { setExitDialogSubmitRef } = useExitFormDialog({
formId
});
const unfulfiledItemsQuantites = useFormset<LineItemData, number>(
getOrderUnfulfilledLines(order).map(getParsedLineData({ initialValue: 0 }))
@ -241,6 +244,14 @@ function useOrderReturnForm(
};
}
const hasAnyItemsSelected =
fulfiledItemsQuatities.data.some(({ value }) => !!value) ||
waitingItemsQuantities.data.some(({ value }) => !!value) ||
unfulfiledItemsQuantites.data.some(({ value }) => !!value);
const isSaveDisabled = !hasAnyItemsSelected;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
@ -259,7 +270,8 @@ function useOrderReturnForm(
handleSetMaximalUnfulfiledItemsQuantities
},
hasChanged,
submit
submit,
isSaveDisabled
};
}

View file

@ -43,8 +43,9 @@ const OrderSettingsPage: React.FC<OrderSettingsPageProps> = props => {
orderSettings={orderSettings}
shop={shop}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, submit, hasChanged, change }) => (
{({ data, submit, change, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.orders)}
@ -73,7 +74,7 @@ const OrderSettingsPage: React.FC<OrderSettingsPageProps> = props => {
<Savebar
onCancel={onBack}
onSubmit={submit}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
/>
</Container>

View file

@ -2,7 +2,10 @@ import {
OrderSettingsFragment,
ShopOrderSettingsFragment
} from "@saleor/graphql";
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
import useForm, {
CommonUseFormResult,
SubmitPromise
} from "@saleor/hooks/useForm";
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
import React from "react";
@ -13,18 +16,15 @@ export interface OrderSettingsFormData {
automaticallyFulfillNonShippableGiftCard: boolean;
}
export interface UseOrderSettingsFormResult {
change: FormChange;
data: OrderSettingsFormData;
hasChanged: boolean;
submit: () => SubmitPromise<any[]>;
}
export type UseOrderSettingsFormResult = CommonUseFormResult<
OrderSettingsFormData
>;
export interface OrderSettingsFormProps {
children: (props: UseOrderSettingsFormResult) => React.ReactNode;
orderSettings: OrderSettingsFragment;
shop: ShopOrderSettingsFragment;
onSubmit: (data: OrderSettingsFormData) => SubmitPromise;
disabled: boolean;
}
function getOrderSeettingsFormData(
@ -44,15 +44,19 @@ function getOrderSeettingsFormData(
function useOrderSettingsForm(
orderSettings: OrderSettingsFragment,
shop: ShopOrderSettingsFragment,
onSubmit: (data: OrderSettingsFormData) => SubmitPromise
onSubmit: (data: OrderSettingsFormData) => SubmitPromise,
disabled: boolean
): UseOrderSettingsFormResult {
const { data, handleChange, formId, hasChanged, setChanged } = useForm(
getOrderSeettingsFormData(orderSettings, shop),
undefined,
{
confirmLeave: true
}
);
const {
data,
handleChange,
formId,
hasChanged,
setChanged,
setIsSubmitDisabled
} = useForm(getOrderSeettingsFormData(orderSettings, shop), undefined, {
confirmLeave: true
});
const handleFormSubmit = useHandleFormSubmit({
formId,
@ -61,12 +65,15 @@ function useOrderSettingsForm(
});
const submit = () => handleFormSubmit(data);
const isSaveDisabled = disabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -74,9 +81,10 @@ const OrderSettingsForm: React.FC<OrderSettingsFormProps> = ({
children,
orderSettings,
shop,
onSubmit
onSubmit,
disabled
}) => {
const props = useOrderSettingsForm(orderSettings, shop, onSubmit);
const props = useOrderSettingsForm(orderSettings, shop, onSubmit, disabled);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -58,8 +58,13 @@ const PageTypeCreatePage: React.FC<PageTypeCreatePageProps> = props => {
} = useMetadataChangeTrigger();
return (
<Form confirmLeave initial={formInitialData} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => {
<Form
confirmLeave
initial={formInitialData}
onSubmit={onSubmit}
disabled={disabled}
>
{({ change, data, submit, isSaveDisabled }) => {
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -103,7 +108,7 @@ const PageTypeCreatePage: React.FC<PageTypeCreatePageProps> = props => {
<Savebar
onCancel={onBack}
onSubmit={submit}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
/>
</Container>

View file

@ -110,8 +110,13 @@ const PageTypeDetailsPage: React.FC<PageTypeDetailsPageProps> = props => {
};
return (
<Form confirmLeave initial={formInitialData} onSubmit={handleSubmit}>
{({ change, data, hasChanged, submit }) => {
<Form
confirmLeave
initial={formInitialData}
onSubmit={handleSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => {
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -174,7 +179,7 @@ const PageTypeDetailsPage: React.FC<PageTypeDetailsPageProps> = props => {
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
/>
</Container>

View file

@ -135,8 +135,9 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
fetchMoreReferenceProducts={fetchMoreReferenceProducts}
assignReferencesAttributeId={assignReferencesAttributeId}
onSubmit={onSubmit}
disabled={loading}
>
{({ change, data, valid, handlers, hasChanged, submit }) => (
{({ change, data, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.pages)}
@ -242,7 +243,7 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={loading || !hasChanged || !valid}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onDelete={page === null ? undefined : onRemove}

View file

@ -97,6 +97,7 @@ export interface PageFormProps extends UsePageFormOpts {
children: (props: UsePageUpdateFormResult) => React.ReactNode;
page: PageDetailsFragment;
onSubmit: (data: PageData) => SubmitPromise;
disabled: boolean;
}
const getInitialFormData = (page?: PageDetailsFragment): PageFormData => ({
@ -114,6 +115,7 @@ const getInitialFormData = (page?: PageDetailsFragment): PageFormData => ({
function usePageForm(
page: PageDetailsFragment,
onSubmit: (data: PageData) => SubmitPromise,
disabled: boolean,
opts: UsePageFormOpts
): UsePageUpdateFormResult {
const pageExists = page !== null;
@ -138,7 +140,7 @@ function usePageForm(
confirmLeave: true
});
const { setExitDialogSubmitRef } = useExitFormDialog({
const { setExitDialogSubmitRef, setIsSubmitDisabled } = useExitFormDialog({
formId
});
@ -238,6 +240,9 @@ function usePageForm(
const valid = pageExists || !!opts.selectedPageType;
const isSaveDisabled = disabled || !hasChanged || !valid;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data: getData(),
@ -255,7 +260,8 @@ function usePageForm(
selectPageType: handlePageTypeSelect
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -263,9 +269,10 @@ const PageForm: React.FC<PageFormProps> = ({
children,
page,
onSubmit,
disabled,
...rest
}) => {
const props = usePageForm(page, onSubmit, rest);
const props = usePageForm(page, onSubmit, disabled, rest);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -55,8 +55,13 @@ const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
);
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ data, change, submit, hasChanged }) => (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data, change, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>
{intl.formatMessage(sectionNames.permissionGroups)}
@ -95,7 +100,7 @@ const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
/>
</div>
</Container>

View file

@ -75,8 +75,9 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = ({
initial={initialFormData()}
onSubmit={onSubmit}
key={selectedChannelId}
disabled={disabled}
>
{({ data, hasChanged, submit, set }) => {
{({ data, submit, set, isSaveDisabled }) => {
const onChange = (event: ChangeEvent) => {
const { name, value } = event.target;
const newData = {
@ -159,7 +160,7 @@ const PluginsDetailsPage: React.FC<PluginsDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -91,8 +91,13 @@ const ProductTypeCreatePage: React.FC<ProductTypeCreatePageProps> = ({
};
return (
<Form confirmLeave initial={initialData} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => {
<Form
confirmLeave
initial={initialData}
onSubmit={onSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => {
const changeMetadata = makeMetadataChangeHandler(change);
const changeKind = makeProductTypeKindChangeHandler(
@ -145,7 +150,7 @@ const ProductTypeCreatePage: React.FC<ProductTypeCreatePageProps> = ({
<Savebar
onCancel={onBack}
onSubmit={submit}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
/>
</Container>

View file

@ -156,8 +156,13 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
};
return (
<Form initial={formInitialData} onSubmit={handleSubmit} confirmLeave>
{({ change, data, hasChanged, submit, setChanged }) => {
<Form
initial={formInitialData}
onSubmit={handleSubmit}
confirmLeave
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit, setChanged }) => {
const changeMetadata = makeMetadataChangeHandler(change);
return (
@ -256,7 +261,7 @@ const ProductTypeDetailsPage: React.FC<ProductTypeDetailsPageProps> = ({
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
/>
</Container>

View file

@ -202,16 +202,9 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
fetchReferenceProducts={fetchReferenceProducts}
fetchMoreReferenceProducts={fetchMoreReferenceProducts}
assignReferencesAttributeId={assignReferencesAttributeId}
loading={loading}
>
{({
change,
data,
formErrors,
disabled: formDisabled,
handlers,
hasChanged,
submit
}) => {
{({ change, data, formErrors, handlers, submit, isSaveDisabled }) => {
// Comparing explicitly to false because `hasVariants` can be undefined
const isSimpleProduct = data.productType?.hasVariants === false;
@ -367,7 +360,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}
disabled={loading || !onSubmit || formDisabled || !hasChanged}
disabled={isSaveDisabled}
/>
{canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog

View file

@ -163,11 +163,13 @@ export interface ProductCreateFormProps extends UseProductCreateFormOpts {
children: (props: UseProductCreateFormResult) => React.ReactNode;
initial?: Partial<ProductCreateFormData>;
onSubmit: (data: ProductCreateData) => SubmitPromise;
loading: boolean;
}
function useProductCreateForm(
initial: Partial<ProductCreateFormData>,
onSubmit: (data: ProductCreateData) => SubmitPromise,
loading: boolean,
opts: UseProductCreateFormOpts
): UseProductCreateFormResult {
const intl = useIntl();
@ -231,10 +233,6 @@ function useProductCreateForm(
triggerChange
});
const { setExitDialogSubmitRef } = useExitFormDialog({
formId: PRODUCT_CREATE_FORM_ID
});
const {
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
@ -357,6 +355,10 @@ function useProductCreateForm(
const submit = () => handleFormSubmit(data);
const { setExitDialogSubmitRef, setIsSubmitDisabled } = useExitFormDialog({
formId: PRODUCT_CREATE_FORM_ID
});
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const shouldEnableSave = () => {
@ -387,12 +389,15 @@ function useProductCreateForm(
return true;
};
const disabled = !shouldEnableSave();
const isSaveEnabled = !shouldEnableSave();
const isSaveDisabled = loading || !onSubmit || isSaveEnabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
disabled,
disabled: isSaveEnabled,
formErrors: form.errors,
handlers: {
addStock: handleStockAdd,
@ -416,7 +421,8 @@ function useProductCreateForm(
selectTaxRate: handleTaxTypeSelect
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -424,9 +430,10 @@ const ProductCreateForm: React.FC<ProductCreateFormProps> = ({
children,
initial,
onSubmit,
loading,
...rest
}) => {
const props = useProductCreateForm(initial || {}, onSubmit, rest);
const props = useProductCreateForm(initial || {}, onSubmit, loading, rest);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -284,16 +284,10 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
fetchReferenceProducts={fetchReferenceProducts}
fetchMoreReferenceProducts={fetchMoreReferenceProducts}
assignReferencesAttributeId={assignReferencesAttributeId}
disabled={disabled}
hasChannelChanged={hasChannelChanged}
>
{({
change,
data,
formErrors,
disabled: formDisabled,
handlers,
hasChanged,
submit
}) => (
{({ change, data, formErrors, handlers, submit, isSaveDisabled }) => (
<>
<Container>
<Backlink onClick={onBack}>
@ -508,9 +502,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onDelete={onDelete}
onSubmit={submit}
state={saveButtonBarState}
disabled={
disabled || formDisabled || (!hasChanged && !hasChannelChanged)
}
disabled={isSaveDisabled}
/>
{canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog

View file

@ -157,7 +157,6 @@ export interface UseProductUpdateFormResult
ProductUpdateData,
ProductUpdateHandlers
> {
disabled: boolean;
formErrors: FormErrors<ProductUpdateSubmitData>;
}
@ -193,6 +192,8 @@ export interface ProductUpdateFormProps extends UseProductUpdateFormOpts {
children: (props: UseProductUpdateFormResult) => React.ReactNode;
product: ProductFragment;
onSubmit: (data: ProductUpdateSubmitData) => SubmitPromise;
disabled: boolean;
hasChannelChanged: boolean;
}
const getStocksData = (
@ -222,6 +223,8 @@ const getStocksData = (
function useProductUpdateForm(
product: ProductFragment,
onSubmit: (data: ProductUpdateSubmitData) => SubmitPromise,
disabled: boolean,
hasChannelChanged: boolean,
opts: UseProductUpdateFormOpts
): UseProductUpdateFormResult {
const intl = useIntl();
@ -244,7 +247,8 @@ function useProductUpdateForm(
toggleValue,
data: formData,
setChanged,
hasChanged
hasChanged,
setIsSubmitDisabled
} = form;
const attributes = useFormset(getAttributeInputFromProduct(product));
@ -435,12 +439,15 @@ function useProductUpdateForm(
return true;
};
const disabled = !shouldEnableSave();
const isSaveEnabled = !shouldEnableSave();
const isSaveDisabled =
disabled || isSaveEnabled || (!hasChanged && !hasChannelChanged);
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
disabled,
formErrors: form.errors,
handlers: {
addStock: handleStockAdd,
@ -464,7 +471,8 @@ function useProductUpdateForm(
selectTaxRate: handleTaxTypeSelect
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -472,9 +480,17 @@ const ProductUpdateForm: React.FC<ProductUpdateFormProps> = ({
children,
product,
onSubmit,
disabled,
hasChannelChanged,
...rest
}) => {
const props = useProductUpdateForm(product, onSubmit, rest);
const props = useProductUpdateForm(
product,
onSubmit,
disabled,
hasChannelChanged,
rest
);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -149,15 +149,9 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
fetchReferenceProducts={fetchReferenceProducts}
fetchMoreReferenceProducts={fetchMoreReferenceProducts}
assignReferencesAttributeId={assignReferencesAttributeId}
disabled={disabled}
>
{({
change,
data,
formErrors,
disabled: formDisabled,
handlers,
submit
}) => (
{({ change, data, formErrors, handlers, submit, isSaveDisabled }) => (
<Container>
<Backlink onClick={onBack}>{product?.name}</Backlink>
<PageHeader title={header} />
@ -258,7 +252,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || formDisabled || !onSubmit}
disabled={isSaveDisabled}
labels={{
confirm: intl.formatMessage(messages.saveVariant),
delete: intl.formatMessage(messages.deleteVariant)

View file

@ -94,6 +94,7 @@ export interface ProductVariantCreateFormProps
children: (props: UseProductVariantCreateFormResult) => React.ReactNode;
product: ProductVariantCreateDataQuery["product"];
onSubmit: (data: ProductVariantCreateData) => void;
disabled: boolean;
}
const initial: ProductVariantCreateFormData = {
@ -113,6 +114,7 @@ const initial: ProductVariantCreateFormData = {
function useProductVariantCreateForm(
product: ProductVariantCreateDataQuery["product"],
onSubmit: (data: ProductVariantCreateData) => void,
disabled: boolean,
opts: UseProductVariantCreateFormOpts
): UseProductVariantCreateFormResult {
const intl = useIntl();
@ -126,7 +128,8 @@ function useProductVariantCreateForm(
handleChange,
hasChanged,
data: formData,
formId
formId,
setIsSubmitDisabled
} = form;
const attributes = useFormset(attributeInput);
@ -227,13 +230,18 @@ function useProductVariantCreateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const formDisabled =
data.isPreorder &&
data.hasPreorderEndDate &&
!!form.errors.preorderEndDateTime;
const isSaveDisabled = disabled || formDisabled || !onSubmit;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
disabled:
data.isPreorder &&
data.hasPreorderEndDate &&
!!form.errors.preorderEndDateTime,
disabled,
formErrors: form.errors,
handlers: {
addStock: handleStockAdd,
@ -250,7 +258,8 @@ function useProductVariantCreateForm(
selectAttributeReference: handleAttributeReferenceChange
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -258,9 +267,10 @@ const ProductVariantCreateForm: React.FC<ProductVariantCreateFormProps> = ({
children,
product,
onSubmit,
disabled,
...rest
}) => {
const props = useProductVariantCreateForm(product, onSubmit, rest);
const props = useProductVariantCreateForm(product, onSubmit, disabled, rest);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -218,16 +218,9 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
fetchReferenceProducts={fetchReferenceProducts}
fetchMoreReferenceProducts={fetchMoreReferenceProducts}
assignReferencesAttributeId={assignReferencesAttributeId}
loading={loading}
>
{({
change,
data,
formErrors,
disabled: formDisabled,
handlers,
hasChanged,
submit
}) => {
{({ change, data, formErrors, isSaveDisabled, handlers, submit }) => {
const nonSelectionAttributes = data.attributes.filter(
byAttributeScope(VariantAttributeScope.NOT_VARIANT_SELECTION)
);
@ -373,7 +366,7 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
</div>
</Grid>
<Savebar
disabled={loading || formDisabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onDelete={onDelete}

View file

@ -130,12 +130,14 @@ export interface ProductVariantUpdateFormProps
extends UseProductVariantUpdateFormOpts {
children: (props: UseProductVariantUpdateFormResult) => React.ReactNode;
variant: ProductVariantFragment;
loading: boolean;
onSubmit: (data: ProductVariantUpdateSubmitData) => SubmitPromise;
}
function useProductVariantUpdateForm(
variant: ProductVariantFragment,
onSubmit: (data: ProductVariantUpdateSubmitData) => SubmitPromise,
loading: boolean,
opts: UseProductVariantUpdateFormOpts
): UseProductVariantUpdateFormResult {
const intl = useIntl();
@ -180,7 +182,8 @@ function useProductVariantUpdateForm(
data: formData,
setChanged,
hasChanged,
formId
formId,
setIsSubmitDisabled
} = form;
const { setExitDialogSubmitRef } = useExitFormDialog({
@ -332,6 +335,9 @@ function useProductVariantUpdateForm(
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
const isSaveDisabled = loading || disabled || !hasChanged;
setIsSubmitDisabled(isSaveDisabled);
return {
change: handleChange,
data,
@ -353,7 +359,8 @@ function useProductVariantUpdateForm(
selectAttributeReference: handleAttributeReferenceChange
},
hasChanged,
submit
submit,
isSaveDisabled
};
}
@ -361,9 +368,10 @@ const ProductVariantUpdateForm: React.FC<ProductVariantUpdateFormProps> = ({
children,
variant,
onSubmit,
loading,
...rest
}) => {
const props = useProductVariantUpdateForm(variant, onSubmit, rest);
const props = useProductVariantUpdateForm(variant, onSubmit, loading, rest);
return <form onSubmit={props.submit}>{children(props)}</form>;
};

View file

@ -122,10 +122,11 @@ export function createHandler(
};
const result = await productCreate(productVariables);
let hasErrors = errors.length > 0;
const hasVariants = productType.hasVariants;
const productId = result.data.productCreate.product?.id;
const hasVariants = productType?.hasVariants;
const productId = result?.data?.productCreate?.product?.id;
if (!productId) {
return { errors };

View file

@ -66,8 +66,13 @@ const ShippingZoneCreatePage: React.FC<ShippingZoneCreatePageProps> = ({
};
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => (
<>
<Container>
<Backlink onClick={onBack}>
@ -105,7 +110,7 @@ const ShippingZoneCreatePage: React.FC<ShippingZoneCreatePageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onSubmit={submit}
state={saveButtonBarState}

View file

@ -124,8 +124,13 @@ const ShippingZoneDetailsPage: React.FC<ShippingZoneDetailsPageProps> = ({
} = useMetadataChangeTrigger();
return (
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
{({ change, data, hasChanged, submit, toggleValue }) => {
<Form
initial={initialForm}
onSubmit={onSubmit}
confirmLeave
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit, toggleValue }) => {
const handleWarehouseChange = createMultiAutocompleteSelectHandler(
toggleValue,
setWarehouseDisplayValues,
@ -215,7 +220,7 @@ const ShippingZoneDetailsPage: React.FC<ShippingZoneDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}

View file

@ -3,7 +3,7 @@ import { ChannelShippingData } from "@saleor/channels/utils";
import CardSpacer from "@saleor/components/CardSpacer";
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import { WithFormId } from "@saleor/components/Form/ExitFormDialogProvider";
import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader";
@ -86,22 +86,30 @@ export const ShippingZoneRatesCreatePage: React.FC<ShippingZoneRatesCreatePagePr
type: null
};
const checkIfSaveIsDisabled = (
data: FormDataWithOpts<ShippingZoneRateCommonFormData>
) => {
const formDisabled = data.channelListings?.some(channel =>
validatePrice(channel.price)
);
return disabled || formDisabled || (!data.hasChanged && !hasChannelChanged);
};
return (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
formId={formId}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, hasChanged, submit, triggerChange, set }) => {
{({ change, data, isSaveDisabled, submit, triggerChange, set }) => {
const handleChannelsChange = createChannelsChangeHandler(
shippingChannels,
onChannelsChange,
triggerChange
);
const formDisabled = data.channelListings?.some(channel =>
validatePrice(channel.price)
);
const onDescriptionChange = (description: OutputData) => {
set({ description });
triggerChange();
@ -181,9 +189,7 @@ export const ShippingZoneRatesCreatePage: React.FC<ShippingZoneRatesCreatePagePr
</div>
</Grid>
<Savebar
disabled={
disabled || formDisabled || (!hasChanged && !hasChannelChanged)
}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}

View file

@ -3,7 +3,7 @@ import { ChannelShippingData } from "@saleor/channels/utils";
import CardSpacer from "@saleor/components/CardSpacer";
import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCard";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Form, { FormDataWithOpts } from "@saleor/components/Form";
import { WithFormId } from "@saleor/components/Form/ExitFormDialogProvider";
import Grid from "@saleor/components/Grid";
import Metadata from "@saleor/components/Metadata/Metadata";
@ -112,30 +112,38 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
makeChangeHandler: makeMetadataChangeHandler
} = useMetadataChangeTrigger();
const checkIfSaveIsDisabled = (
data: FormDataWithOpts<ShippingZoneRateUpdateFormData>
) => {
const formDisabled = data.channelListings?.some(channel =>
validatePrice(channel.price)
);
const formIsUnchanged =
!data.hasChanged && !hasChannelChanged && !havePostalCodesChanged;
return disabled || formDisabled || formIsUnchanged;
};
return (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
formId={formId}
checkIfSaveIsDisabled={checkIfSaveIsDisabled}
>
{({ change, data, hasChanged, submit, set, triggerChange }) => {
{({ change, data, isSaveDisabled, submit, set, triggerChange }) => {
const handleChannelsChange = createChannelsChangeHandler(
shippingChannels,
onChannelsChange,
triggerChange
);
const formDisabled = data.channelListings?.some(channel =>
validatePrice(channel.price)
);
const onDescriptionChange = (description: OutputData) => {
set({ description });
triggerChange();
};
const changeMetadata = makeMetadataChangeHandler(change);
const formIsUnchanged =
!hasChanged && !hasChannelChanged && !havePostalCodesChanged;
return (
<Container>
@ -212,7 +220,7 @@ export const ShippingZoneRatesPage: React.FC<ShippingZoneRatesPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || formDisabled || formIsUnchanged}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}

View file

@ -134,8 +134,9 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
return submitFunc(data);
}}
confirmLeave
disabled={disabled}
>
{({ change, data, hasChanged, submit }) => {
{({ change, data, isSaveDisabled, submit }) => {
const countryChoices = mapCountriesToChoices(shop?.countries || []);
const handleCountryChange = createSingleAutocompleteSelectHandler(
change,
@ -201,7 +202,7 @@ const SiteSettingsPage: React.FC<SiteSettingsPageProps> = props => {
</Grid>
<Savebar
state={saveButtonBarState}
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onSubmit={submit}
/>

View file

@ -101,8 +101,13 @@ const StaffDetailsPage: React.FC<StaffDetailsPageProps> = ({
};
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ data: formData, change, hasChanged, submit, toggleValue }) => {
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ data: formData, change, isSaveDisabled, submit, toggleValue }) => {
const permissionGroupsChange = createMultiAutocompleteSelectHandler(
toggleValue,
setPermissionGroupsDisplayValues,
@ -187,7 +192,7 @@ const StaffDetailsPage: React.FC<StaffDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -46,8 +46,13 @@ const CountryListPage: React.FC<CountryListPageProps> = ({
showGross: maybe(() => shop.displayGrossPrices, false)
};
return (
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
{({ change, data, hasChanged, submit }) => (
<Form
confirmLeave
initial={initialForm}
onSubmit={onSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit }) => (
<>
<Container>
<Backlink onClick={onBack}>
@ -77,7 +82,7 @@ const CountryListPage: React.FC<CountryListPageProps> = ({
</Grid>
</Container>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
state={saveButtonBarState}
onCancel={onBack}
onSubmit={submit}

View file

@ -84,8 +84,13 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
};
return (
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
{({ change, data, hasChanged, submit, set }) => {
<Form
confirmLeave
initial={initialForm}
onSubmit={handleSubmit}
disabled={disabled}
>
{({ change, data, isSaveDisabled, submit, set }) => {
const countryChoices = mapCountriesToChoices(countries);
const handleCountryChange = createSingleAutocompleteSelectHandler(
change,
@ -134,7 +139,7 @@ const WarehouseDetailsPage: React.FC<WarehouseDetailsPageProps> = ({
</div>
</Grid>
<Savebar
disabled={disabled || !hasChanged}
disabled={isSaveDisabled}
onCancel={onBack}
onDelete={onDelete}
onSubmit={submit}