Exit dirty form (#1816)
* Add Exit form prompt component and change some minor styles in other components to match * Add Exit form prompt provider * Adjust generic form and useform hook to allow using exit form prompt provider * Add exit form prompt provider to index * wip * Fix types * Fix styling * Fix types * Revert warehouse details refactor * Add handling of edge cases to exit prompt * Refactor, add comments, fix some types * Refactor after exit form dialog name change * fix types * Fixes after review * Add default value for useform prop opts so the app doesn't crash * Add missing category prop to getting initial data for category details form * Add exit dialog to everywhere WIP (#1600) * Add Exit form prompt component and change some minor styles in other components to match * Add Exit form prompt provider * Adjust generic form and useform hook to allow using exit form prompt provider * Add exit form prompt provider to index * wip * Fix types * Fix styling * Fix types * Revert warehouse details refactor * Add handling of edge cases to exit prompt * Refactor, add comments, fix some types * Refactor after exit form dialog name change * fix types * Add CommonUseFormResultWithHandlers type for later use and refactor handleFormSubmit util * Refactor login form not to use custom form since it doesn't need to * Add exit form dialog to order refund page * Add exit form dialog to order return page * Add exit form dialog to order order settings page * Add exit form dialog to product variant page * Add exit form dialog to product create page * Add exit form dialog to product update page * Add exit form dialog to product variant create page * Fix confirm leave prop passing in generic Form * Add util function to handle for submit to extract errors * Add confirmLeave prop to generic forms * Move handleChange for custom forms to useForm * Add exit dialog to more forms * Add extract mutation errors util function * Add extracting errors to submit functions that use metadata create handler * Fix typo * Add missing category prop to getting initial data for category details form * Fix types * wip * wip * wip * wip * Fix types & refactor * Fix types & refactor * Fix typescript * Fix unmatching tag * Fixes * Add handling of multiple forms at once to exit dirty form provider * Change all usages of ExitFormDialogContext to designated hook * wip * wip * wip * Fix types wip * Fix types * Remove console logs * Add isSubmitting prop to exit form dialog in order to avoid enabling exit dialog while submit is still in progresS * Replace handleSubmit global util with a hook to use exit form dialog props inside * Move useHandleSubmit to general hooks dir, update imports * Small fixes * Update snapshots * Fix types * Small fixes due to extensive rebase * Update package lock * Fixes after rebase * Remove exit form from customer address dialog * Fix types and update messages * Fix types * Change imports names * Refactor * Remove unnecessary console.log * Update types, snapshots. etc after rebase
This commit is contained in:
parent
9b1fce078a
commit
3d636f4789
185 changed files with 3214 additions and 1852 deletions
|
@ -2194,6 +2194,22 @@
|
||||||
"context": "filters error messages value required",
|
"context": "filters error messages value required",
|
||||||
"string": "Choose a value"
|
"string": "Choose a value"
|
||||||
},
|
},
|
||||||
|
"src_dot_components_dot_Form_dot_cancelButton": {
|
||||||
|
"context": "ExitFormPrompt cancel button",
|
||||||
|
"string": "leave without saving"
|
||||||
|
},
|
||||||
|
"src_dot_components_dot_Form_dot_confirmButton": {
|
||||||
|
"context": "ExitFormPrompt confirm button",
|
||||||
|
"string": "save & continue"
|
||||||
|
},
|
||||||
|
"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_title": {
|
||||||
|
"context": "ExitFormPrompt title",
|
||||||
|
"string": "Are you sure you want to leave?"
|
||||||
|
},
|
||||||
"src_dot_components_dot_ImageUpload_dot_1731007575": {
|
"src_dot_components_dot_ImageUpload_dot_1731007575": {
|
||||||
"context": "image upload",
|
"context": "image upload",
|
||||||
"string": "Drop here to upload"
|
"string": "Drop here to upload"
|
||||||
|
@ -5382,20 +5398,12 @@
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_4085755992": {
|
"src_dot_orders_dot_views_dot_OrderDetails_dot_4085755992": {
|
||||||
"string": "Invoice email sent"
|
"string": "Invoice email sent"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_4207717971": {
|
|
||||||
"context": "snackbar title",
|
|
||||||
"string": "Could not generate invoice"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_55607988": {
|
"src_dot_orders_dot_views_dot_OrderDetails_dot_55607988": {
|
||||||
"string": "Invoice is Generating"
|
"string": "Invoice is Generating"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_617145655": {
|
"src_dot_orders_dot_views_dot_OrderDetails_dot_617145655": {
|
||||||
"string": "Shipping method successfully updated"
|
"string": "Shipping method successfully updated"
|
||||||
},
|
},
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_811176136": {
|
|
||||||
"context": "error message",
|
|
||||||
"string": "No invoice plugin installed"
|
|
||||||
},
|
|
||||||
"src_dot_orders_dot_views_dot_OrderDetails_dot_927945225": {
|
"src_dot_orders_dot_views_dot_OrderDetails_dot_927945225": {
|
||||||
"string": "Fulfillment successfully cancelled"
|
"string": "Fulfillment successfully cancelled"
|
||||||
},
|
},
|
||||||
|
@ -8371,6 +8379,18 @@
|
||||||
"context": "section header",
|
"context": "section header",
|
||||||
"string": "Webhook Status"
|
"string": "Webhook Status"
|
||||||
},
|
},
|
||||||
|
"src_dot_webhooks_dot_components_dot_WebhooksDetailsPage_dot_header": {
|
||||||
|
"context": "header",
|
||||||
|
"string": "Unnamed Webhook Details"
|
||||||
|
},
|
||||||
|
"src_dot_webhooks_dot_components_dot_WebhooksDetailsPage_dot_headerCreate": {
|
||||||
|
"context": "header",
|
||||||
|
"string": "Create Webhook"
|
||||||
|
},
|
||||||
|
"src_dot_webhooks_dot_components_dot_WebhooksDetailsPage_dot_headerNamed": {
|
||||||
|
"context": "header",
|
||||||
|
"string": "{webhookName} Details"
|
||||||
|
},
|
||||||
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_1153324159": {
|
"src_dot_webhooks_dot_components_dot_WebhooksList_dot_1153324159": {
|
||||||
"string": "No webhooks found"
|
"string": "No webhooks found"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
|
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
|
||||||
import plusIcon from "@assets/images/plus-icon.svg";
|
import plusIcon from "@assets/images/plus-icon.svg";
|
||||||
import { Card, CardContent, Grid, Typography } from "@material-ui/core";
|
import { Card, CardContent, Grid, Typography } from "@material-ui/core";
|
||||||
|
import { AppInstall_appInstall_errors } from "@saleor/apps/types/AppInstall";
|
||||||
import CardSpacer from "@saleor/components/CardSpacer";
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { Button } from "@saleor/macaw-ui";
|
import { Button } from "@saleor/macaw-ui";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -19,7 +21,7 @@ export interface AppInstallPageProps {
|
||||||
data: AppFetch_appFetchManifest_manifest;
|
data: AppFetch_appFetchManifest_manifest;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
navigateToAppsList: () => void;
|
navigateToAppsList: () => void;
|
||||||
onSubmit: () => void;
|
onSubmit: () => SubmitPromise<AppInstall_appInstall_errors[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppInstallPage: React.FC<AppInstallPageProps> = ({
|
export const AppInstallPage: React.FC<AppInstallPageProps> = ({
|
||||||
|
|
|
@ -6,6 +6,7 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
|
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
|
||||||
import { AppErrorFragment } from "@saleor/fragments/types/AppErrorFragment";
|
import { AppErrorFragment } from "@saleor/fragments/types/AppErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -28,7 +29,9 @@ export interface CustomAppCreatePageProps {
|
||||||
permissions: ShopInfo_shop_permissions[];
|
permissions: ShopInfo_shop_permissions[];
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: CustomAppCreatePageFormData) => void;
|
onSubmit: (
|
||||||
|
data: CustomAppCreatePageFormData
|
||||||
|
) => SubmitPromise<AppErrorFragment[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
|
const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
|
||||||
|
@ -52,7 +55,7 @@ const CustomAppCreatePage: React.FC<CustomAppCreatePageProps> = props => {
|
||||||
const permissionsError = getAppErrorMessage(formErrors.permissions, intl);
|
const permissionsError = getAppErrorMessage(formErrors.permissions, intl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ data, change, hasChanged, submit }) => (
|
{({ data, change, hasChanged, submit }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Backlink onClick={onBack}>
|
<Backlink onClick={onBack}>
|
||||||
|
|
|
@ -44,7 +44,9 @@ export interface CustomAppDetailsPageProps {
|
||||||
onTokenDelete: (id: string) => void;
|
onTokenDelete: (id: string) => void;
|
||||||
onTokenClose: () => void;
|
onTokenClose: () => void;
|
||||||
onTokenCreate: () => void;
|
onTokenCreate: () => void;
|
||||||
onSubmit: (data: CustomAppDetailsPageFormData) => SubmitPromise;
|
onSubmit: (
|
||||||
|
data: CustomAppDetailsPageFormData
|
||||||
|
) => SubmitPromise<AppErrorFragment[]>;
|
||||||
onWebhookCreate: () => void;
|
onWebhookCreate: () => void;
|
||||||
onWebhookRemove: (id: string) => void;
|
onWebhookRemove: (id: string) => void;
|
||||||
navigateToWebhookDetails: (id: string) => () => void;
|
navigateToWebhookDetails: (id: string) => () => void;
|
||||||
|
@ -94,7 +96,7 @@ const CustomAppDetailsPage: React.FC<CustomAppDetailsPageProps> = props => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ data, change, hasChanged, submit }) => (
|
{({ data, change, hasChanged, submit }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Backlink onClick={onBack}>
|
<Backlink onClick={onBack}>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import ConfirmButton from "@saleor/components/ConfirmButton";
|
import ConfirmButton from "@saleor/components/ConfirmButton";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
import { Button, ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { Button, ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
|
@ -25,7 +26,7 @@ export interface TokenCreateDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
token: string | undefined;
|
token: string | undefined;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onCreate: (name: string) => void;
|
onCreate: (name: string) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenCreateStep = "form" | "summary";
|
type TokenCreateStep = "form" | "summary";
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
import useLocalStorage from "@saleor/hooks/useLocalStorage";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import getAppErrorMessage from "@saleor/utils/errors/app";
|
import getAppErrorMessage from "@saleor/utils/errors/app";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
@ -67,15 +68,19 @@ export const InstallAppCreate: React.FC<InstallAppCreateProps> = ({
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const manifest = fetchManifestOpts?.data?.appFetchManifest?.manifest;
|
const manifest = fetchManifestOpts?.data?.appFetchManifest?.manifest;
|
||||||
|
return extractMutationErrors(
|
||||||
installApp({
|
installApp({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
appName: manifest?.name,
|
appName: manifest?.name,
|
||||||
manifestUrl,
|
manifestUrl,
|
||||||
permissions: manifest?.permissions.map(permission => permission.code)
|
permissions: manifest?.permissions.map(
|
||||||
|
permission => permission.code
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -41,7 +42,8 @@ export const CustomAppCreate: React.FC<CustomAppCreateProps> = ({
|
||||||
onCompleted: onSubmit
|
onCompleted: onSubmit
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (data: CustomAppCreatePageFormData) =>
|
const handleSubmit = async (data: CustomAppCreatePageFormData) =>
|
||||||
|
extractMutationErrors(
|
||||||
createApp({
|
createApp({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
@ -51,7 +53,8 @@ export const CustomAppCreate: React.FC<CustomAppCreateProps> = ({
|
||||||
: data.permissions
|
: data.permissions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
import { extractMutationErrors, getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import getAppErrorMessage from "@saleor/utils/errors/app";
|
import getAppErrorMessage from "@saleor/utils/errors/app";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog";
|
import WebhookDeleteDialog from "@saleor/webhooks/components/WebhookDeleteDialog";
|
||||||
|
@ -181,8 +181,9 @@ export const CustomAppDetails: React.FC<OrderListProps> = ({
|
||||||
onCompleted: onTokenDelete
|
onCompleted: onTokenDelete
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async (data: CustomAppDetailsPageFormData) => {
|
const handleSubmit = async (data: CustomAppDetailsPageFormData) =>
|
||||||
const result = await updateApp({
|
extractMutationErrors(
|
||||||
|
updateApp({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input: {
|
input: {
|
||||||
|
@ -192,10 +193,8 @@ export const CustomAppDetails: React.FC<OrderListProps> = ({
|
||||||
: data.permissions
|
: data.permissions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return result.data.appUpdate.errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTokenCreate = (name: string) =>
|
const handleTokenCreate = (name: string) =>
|
||||||
createToken({
|
createToken({
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Savebar from "@saleor/components/Savebar";
|
||||||
import { ListSettingsUpdate } from "@saleor/components/TablePagination";
|
import { ListSettingsUpdate } from "@saleor/components/TablePagination";
|
||||||
import { AttributeDetailsFragment } from "@saleor/fragments/types/AttributeDetailsFragment";
|
import { AttributeDetailsFragment } from "@saleor/fragments/types/AttributeDetailsFragment";
|
||||||
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
import { AttributeErrorFragment } from "@saleor/fragments/types/AttributeErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -41,7 +42,7 @@ export interface AttributePageProps {
|
||||||
values: AttributeDetails_attribute_choices;
|
values: AttributeDetails_attribute_choices;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
onSubmit: (data: AttributePageFormData) => void;
|
onSubmit: (data: AttributePageFormData) => SubmitPromise;
|
||||||
onValueAdd: () => void;
|
onValueAdd: () => void;
|
||||||
onValueDelete: (id: string) => void;
|
onValueDelete: (id: string) => void;
|
||||||
onValueReorder: ReorderAction;
|
onValueReorder: ReorderAction;
|
||||||
|
@ -156,7 +157,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
|
||||||
{({
|
{({
|
||||||
change,
|
change,
|
||||||
set,
|
set,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import useListSettings from "@saleor/hooks/useListSettings";
|
||||||
import useLocalPageInfo, { getMaxPage } from "@saleor/hooks/useLocalPageInfo";
|
import useLocalPageInfo, { getMaxPage } from "@saleor/hooks/useLocalPageInfo";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
import { getMutationErrors, getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import { ListViews, ReorderEvent } from "@saleor/types";
|
import { ListViews, ReorderEvent } from "@saleor/types";
|
||||||
import { AttributeErrorCode } from "@saleor/types/globalTypes";
|
import { AttributeErrorCode } from "@saleor/types/globalTypes";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
@ -154,16 +154,18 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreate = async (data: AttributePageFormData) => {
|
const handleCreate = async (data: AttributePageFormData) => {
|
||||||
const input = getAttributeData(data, values);
|
|
||||||
|
|
||||||
const result = await attributeCreate({
|
const result = await attributeCreate({
|
||||||
variables: {
|
variables: {
|
||||||
input
|
input: getAttributeData(data, values)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data.attributeCreate?.attribute?.id || null;
|
return {
|
||||||
|
id: result.data.attributeCreate?.attribute?.id || null,
|
||||||
|
errors: getMutationErrors(result)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataCreateHandler(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
handleCreate,
|
handleCreate,
|
||||||
updateMetadata,
|
updateMetadata,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import useLocalPaginator, {
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import { ListViews, ReorderEvent } from "@saleor/types";
|
import { ListViews, ReorderEvent } from "@saleor/types";
|
||||||
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
import getAttributeErrorMessage from "@saleor/utils/errors/attribute";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
@ -15,6 +16,7 @@ import {
|
||||||
useMetadataUpdate,
|
useMetadataUpdate,
|
||||||
usePrivateMetadataUpdate
|
usePrivateMetadataUpdate
|
||||||
} from "@saleor/utils/metadata/updateMetadata";
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
|
import omit from "lodash/omit";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -209,25 +211,23 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleUpdate = async (data: AttributePageFormData) => {
|
const handleUpdate = async (data: AttributePageFormData) =>
|
||||||
const input = {
|
extractMutationErrors(
|
||||||
...data,
|
attributeUpdate({
|
||||||
entityType: undefined,
|
|
||||||
inputType: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
privateMetadata: undefined,
|
|
||||||
storefrontSearchPosition: parseInt(data?.storefrontSearchPosition, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await attributeUpdate({
|
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input
|
input: {
|
||||||
|
...omit(data, [
|
||||||
|
"entityType",
|
||||||
|
"inputType",
|
||||||
|
"metadata",
|
||||||
|
"privateMetadata"
|
||||||
|
]),
|
||||||
|
storefrontSearchPosition: parseInt(data.storefrontSearchPosition, 0)
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
})
|
||||||
return result.data?.attributeUpdate.errors;
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
data?.attribute,
|
data?.attribute,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DEMO_MODE } from "@saleor/config";
|
import { DEMO_MODE } from "@saleor/config";
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface LoginFormData {
|
export interface LoginFormData {
|
||||||
|
@ -12,7 +12,7 @@ export interface UseLoginFormResult {
|
||||||
change: FormChange;
|
change: FormChange;
|
||||||
data: LoginFormData;
|
data: LoginFormData;
|
||||||
hasChanged: boolean;
|
hasChanged: boolean;
|
||||||
submit: () => Promise<boolean>;
|
submit: () => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginFormProps {
|
export interface LoginFormProps {
|
||||||
|
@ -33,32 +33,18 @@ const getLoginFormData = () => {
|
||||||
function useLoginForm(
|
function useLoginForm(
|
||||||
onSubmit: (data: LoginFormData) => SubmitPromise
|
onSubmit: (data: LoginFormData) => SubmitPromise
|
||||||
): UseLoginFormResult {
|
): UseLoginFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm(getLoginFormData());
|
const form = useForm(getLoginFormData());
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
const { change, hasChanged, data, setChanged } = form;
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
const data: LoginFormData = {
|
const handleFormSubmit = useHandleFormSubmit({ onSubmit, setChanged });
|
||||||
...form.data
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (data: LoginFormData) => {
|
const submit = async () => handleFormSubmit(data);
|
||||||
const errors = await onSubmit(data);
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = async () => handleFormSubmit(data, handleSubmit, setChanged);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change,
|
||||||
data,
|
data,
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
4
src/auth/components/LoginPage/types.ts
Normal file
4
src/auth/components/LoginPage/types.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface LoginFormData {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { TextField, Typography } from "@material-ui/core";
|
import { TextField, Typography } from "@material-ui/core";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { Button } from "@saleor/macaw-ui";
|
import { Button } from "@saleor/macaw-ui";
|
||||||
import { SetPasswordData } from "@saleor/sdk";
|
import { SetPasswordData } from "@saleor/sdk";
|
||||||
import getAccountErrorMessage from "@saleor/utils/errors/account";
|
import getAccountErrorMessage from "@saleor/utils/errors/account";
|
||||||
|
@ -16,7 +17,7 @@ export interface NewPasswordPageFormData {
|
||||||
export interface NewPasswordPageProps {
|
export interface NewPasswordPageProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
errors: SetPasswordData["errors"];
|
errors: SetPasswordData["errors"];
|
||||||
onSubmit: (data: NewPasswordPageFormData) => void;
|
onSubmit: (data: NewPasswordPageFormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialForm: NewPasswordPageFormData = {
|
const initialForm: NewPasswordPageFormData = {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { TextField, Typography } from "@material-ui/core";
|
import { TextField, Typography } from "@material-ui/core";
|
||||||
|
import { RequestPasswordReset_requestPasswordReset_errors } from "@saleor/auth/types/RequestPasswordReset";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import FormSpacer from "@saleor/components/FormSpacer";
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { ArrowRightIcon, Button, IconButton } from "@saleor/macaw-ui";
|
import { ArrowRightIcon, Button, IconButton } from "@saleor/macaw-ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -15,7 +17,9 @@ export interface ResetPasswordPageProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: ResetPasswordPageFormData) => void;
|
onSubmit: (
|
||||||
|
data: ResetPasswordPageFormData
|
||||||
|
) => SubmitPromise<RequestPasswordReset_requestPasswordReset_errors[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
|
const ResetPasswordPage: React.FC<ResetPasswordPageProps> = props => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import useRouter from "use-react-router";
|
||||||
|
|
||||||
import { useUser } from "..";
|
import { useUser } from "..";
|
||||||
import LoginPage from "../components/LoginPage";
|
import LoginPage from "../components/LoginPage";
|
||||||
import { LoginFormData } from "../components/LoginPage/form";
|
import { LoginFormData } from "../components/LoginPage/types";
|
||||||
import { availableExternalAuthentications } from "../queries";
|
import { availableExternalAuthentications } from "../queries";
|
||||||
import { AvailableExternalAuthentications } from "../types/AvailableExternalAuthentications";
|
import { AvailableExternalAuthentications } from "../types/AvailableExternalAuthentications";
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { APP_MOUNT_URI } from "@saleor/config";
|
import { APP_MOUNT_URI } from "@saleor/config";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
import urlJoin from "url-join";
|
import urlJoin from "url-join";
|
||||||
|
@ -38,6 +39,7 @@ const ResetPasswordView: React.FC = () => {
|
||||||
<RequestPasswordResetMutation onCompleted={handleRequestPasswordReset}>
|
<RequestPasswordResetMutation onCompleted={handleRequestPasswordReset}>
|
||||||
{(requestPasswordReset, requestPasswordResetOpts) => {
|
{(requestPasswordReset, requestPasswordResetOpts) => {
|
||||||
const handleSubmit = (data: ResetPasswordPageFormData) =>
|
const handleSubmit = (data: ResetPasswordPageFormData) =>
|
||||||
|
extractMutationErrors(
|
||||||
requestPasswordReset({
|
requestPasswordReset({
|
||||||
variables: {
|
variables: {
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
@ -47,7 +49,8 @@ const ResetPasswordView: React.FC = () => {
|
||||||
newPasswordUrl().replace(/\?/, "")
|
newPasswordUrl().replace(/\?/, "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResetPasswordPage
|
<ResetPasswordPage
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
CommonUseFormResult,
|
||||||
|
FormChange
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import useRichText from "@saleor/utils/richText/useRichText";
|
import useRichText from "@saleor/utils/richText/useRichText";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export interface CategoryCreateFormData extends MetadataFormData {
|
export interface CategoryCreateFormData extends MetadataFormData {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -21,12 +25,9 @@ interface CategoryCreateHandlers {
|
||||||
changeMetadata: FormChange;
|
changeMetadata: FormChange;
|
||||||
changeDescription: RichTextEditorChange;
|
changeDescription: RichTextEditorChange;
|
||||||
}
|
}
|
||||||
export interface UseCategoryCreateFormResult {
|
export interface UseCategoryCreateFormResult
|
||||||
change: FormChange;
|
extends CommonUseFormResult<CategoryCreateData> {
|
||||||
data: CategoryCreateData;
|
|
||||||
handlers: CategoryCreateHandlers;
|
handlers: CategoryCreateHandlers;
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryCreateFormProps {
|
export interface CategoryCreateFormProps {
|
||||||
|
@ -34,20 +35,37 @@ export interface CategoryCreateFormProps {
|
||||||
onSubmit: (data: CategoryCreateData) => Promise<any[]>;
|
onSubmit: (data: CategoryCreateData) => Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCategoryCreateForm(
|
const initialData: CategoryCreateFormData = {
|
||||||
onSubmit: (data: CategoryCreateData) => Promise<any[]>
|
|
||||||
): UseCategoryCreateFormResult {
|
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm<CategoryCreateFormData>({
|
|
||||||
metadata: [],
|
metadata: [],
|
||||||
name: "",
|
name: "",
|
||||||
privateMetadata: [],
|
privateMetadata: [],
|
||||||
seoDescription: "",
|
seoDescription: "",
|
||||||
seoTitle: "",
|
seoTitle: "",
|
||||||
slug: ""
|
slug: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
function useCategoryCreateForm(
|
||||||
|
onSubmit: (data: CategoryCreateData) => Promise<any[]>
|
||||||
|
): UseCategoryCreateFormResult {
|
||||||
|
const {
|
||||||
|
handleChange,
|
||||||
|
data,
|
||||||
|
hasChanged,
|
||||||
|
triggerChange,
|
||||||
|
setChanged,
|
||||||
|
formId
|
||||||
|
} = useForm(initialData, undefined, { confirmLeave: true });
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: null,
|
initial: null,
|
||||||
triggerChange
|
triggerChange
|
||||||
|
@ -57,19 +75,17 @@ function useCategoryCreateForm(
|
||||||
makeChangeHandler: makeMetadataChangeHandler
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||||
|
|
||||||
// Need to make it function to always have description.current up to date
|
// Need to make it function to always have description.current up to date
|
||||||
const getData = (): CategoryCreateData => ({
|
const getData = (): CategoryCreateData => ({
|
||||||
...form.data,
|
...data,
|
||||||
description: description.current
|
description: description.current
|
||||||
});
|
});
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getData());
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
|
@ -78,7 +94,7 @@ function useCategoryCreateForm(
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
import { CategoryDetails_category } from "@saleor/categories/types/CategoryDetails";
|
import { CategoryDetails_category } from "@saleor/categories/types/CategoryDetails";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
CommonUseFormResult,
|
||||||
|
FormChange
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import useRichText from "@saleor/utils/richText/useRichText";
|
import useRichText from "@saleor/utils/richText/useRichText";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export interface CategoryUpdateFormData extends MetadataFormData {
|
export interface CategoryUpdateFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
|
@ -25,12 +29,9 @@ interface CategoryUpdateHandlers {
|
||||||
changeMetadata: FormChange;
|
changeMetadata: FormChange;
|
||||||
changeDescription: RichTextEditorChange;
|
changeDescription: RichTextEditorChange;
|
||||||
}
|
}
|
||||||
export interface UseCategoryUpdateFormResult {
|
export interface UseCategoryUpdateFormResult
|
||||||
change: FormChange;
|
extends CommonUseFormResult<CategoryUpdateData> {
|
||||||
data: CategoryUpdateData;
|
|
||||||
handlers: CategoryUpdateHandlers;
|
handlers: CategoryUpdateHandlers;
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryUpdateFormProps {
|
export interface CategoryUpdateFormProps {
|
||||||
|
@ -39,14 +40,7 @@ export interface CategoryUpdateFormProps {
|
||||||
onSubmit: (data: CategoryUpdateData) => Promise<any[]>;
|
onSubmit: (data: CategoryUpdateData) => Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCategoryUpdateForm(
|
const getInitialData = (category?: CategoryDetails_category) => ({
|
||||||
category: CategoryDetails_category,
|
|
||||||
onSubmit: (data: CategoryUpdateData) => Promise<any[]>
|
|
||||||
): UseCategoryUpdateFormResult {
|
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm<CategoryUpdateFormData>({
|
|
||||||
backgroundImageAlt: category?.backgroundImage?.alt || "",
|
backgroundImageAlt: category?.backgroundImage?.alt || "",
|
||||||
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
metadata: category?.metadata?.map(mapMetadataItemToInput),
|
||||||
name: category?.name || "",
|
name: category?.name || "",
|
||||||
|
@ -55,6 +49,30 @@ function useCategoryUpdateForm(
|
||||||
seoTitle: category?.seoTitle || "",
|
seoTitle: category?.seoTitle || "",
|
||||||
slug: category?.slug || ""
|
slug: category?.slug || ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function useCategoryUpdateForm(
|
||||||
|
category: CategoryDetails_category,
|
||||||
|
onSubmit: (data: CategoryUpdateData) => Promise<any[]>
|
||||||
|
): UseCategoryUpdateFormResult {
|
||||||
|
const {
|
||||||
|
handleChange,
|
||||||
|
data,
|
||||||
|
triggerChange,
|
||||||
|
hasChanged,
|
||||||
|
setChanged,
|
||||||
|
formId
|
||||||
|
} = useForm(getInitialData(category), undefined, { confirmLeave: true });
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: category?.description,
|
initial: category?.description,
|
||||||
triggerChange
|
triggerChange
|
||||||
|
@ -66,24 +84,22 @@ function useCategoryUpdateForm(
|
||||||
makeChangeHandler: makeMetadataChangeHandler
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||||
|
|
||||||
// Need to make it function to always have description.current up to date
|
// Need to make it function to always have description.current up to date
|
||||||
const getData = (): CategoryUpdateData => ({
|
const getData = (): CategoryUpdateData => ({
|
||||||
...form.data,
|
...data,
|
||||||
description: description.current
|
description: description.current
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSubmitData = (): CategoryUpdateData => ({
|
const getSubmitData = (): CategoryUpdateData => ({
|
||||||
...getData(),
|
...getData(),
|
||||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
|
...getMetadata(data, isMetadataModified, isPrivateMetadataModified)
|
||||||
});
|
});
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getSubmitData());
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
|
@ -92,7 +108,7 @@ function useCategoryUpdateForm(
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { getMutationErrors } from "@saleor/misc";
|
||||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||||
import {
|
import {
|
||||||
useMetadataUpdate,
|
useMetadataUpdate,
|
||||||
|
@ -61,8 +62,12 @@ export const CategoryCreateView: React.FC<CategoryCreateViewProps> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data?.categoryCreate.category?.id || null;
|
return {
|
||||||
|
id: result.data?.categoryCreate.category?.id || null,
|
||||||
|
errors: getMutationErrors(result)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataCreateHandler(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
handleCreate,
|
handleCreate,
|
||||||
updateMetadata,
|
updateMetadata,
|
||||||
|
|
|
@ -22,7 +22,7 @@ import React, { useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { PAGINATE_BY } from "../../config";
|
import { PAGINATE_BY } from "../../config";
|
||||||
import { maybe } from "../../misc";
|
import { extractMutationErrors, maybe } from "../../misc";
|
||||||
import { useProductBulkDeleteMutation } from "../../products/mutations";
|
import { useProductBulkDeleteMutation } from "../../products/mutations";
|
||||||
import { productAddUrl, productUrl } from "../../products/urls";
|
import { productAddUrl, productUrl } from "../../products/urls";
|
||||||
import { CategoryInput } from "../../types/globalTypes";
|
import { CategoryInput } from "../../types/globalTypes";
|
||||||
|
@ -178,8 +178,9 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
paginationState
|
paginationState
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUpdate = async (formData: CategoryUpdateData) => {
|
const handleUpdate = async (formData: CategoryUpdateData) =>
|
||||||
const result = await updateCategory({
|
extractMutationErrors(
|
||||||
|
updateCategory({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input: {
|
input: {
|
||||||
|
@ -193,10 +194,9 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
slug: formData.slug
|
slug: formData.slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return result.data.categoryUpdate.errors;
|
|
||||||
};
|
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
data?.category,
|
data?.category,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { countries } from "@saleor/fixtures";
|
import { countries } from "@saleor/fixtures";
|
||||||
|
import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragment";
|
||||||
import Decorator from "@saleor/storybook/Decorator";
|
import Decorator from "@saleor/storybook/Decorator";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -8,7 +9,7 @@ import ChannelDetailsPage, {
|
||||||
ChannelDetailsPageProps
|
ChannelDetailsPageProps
|
||||||
} from "./ChannelDetailsPage";
|
} from "./ChannelDetailsPage";
|
||||||
|
|
||||||
const props: ChannelDetailsPageProps = {
|
const props: ChannelDetailsPageProps<ChannelErrorFragment[]> = {
|
||||||
currencyCodes: [
|
currencyCodes: [
|
||||||
{ label: "USD", value: "USD" },
|
{ label: "USD", value: "USD" },
|
||||||
{ label: "PLN", value: "PLN" }
|
{ label: "PLN", value: "PLN" }
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ChannelErrorFragment } from "@saleor/fragments/types/ChannelErrorFragme
|
||||||
import { CountryFragment } from "@saleor/fragments/types/CountryFragment";
|
import { CountryFragment } from "@saleor/fragments/types/CountryFragment";
|
||||||
import { SearchData } from "@saleor/hooks/makeTopLevelSearch";
|
import { SearchData } from "@saleor/hooks/makeTopLevelSearch";
|
||||||
import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils";
|
import { getParsedSearchData } from "@saleor/hooks/makeTopLevelSearch/utils";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import {
|
import {
|
||||||
|
@ -27,7 +28,7 @@ import { Channel_channel } from "../../types/Channel";
|
||||||
import { ChannelShippingZones } from "./types";
|
import { ChannelShippingZones } from "./types";
|
||||||
import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils";
|
import { getUpdatedIdsWithNewId, getUpdatedIdsWithoutNewId } from "./utils";
|
||||||
|
|
||||||
export interface ChannelDetailsPageProps {
|
export interface ChannelDetailsPageProps<TErrors> {
|
||||||
channel?: Channel_channel;
|
channel?: Channel_channel;
|
||||||
currencyCodes?: SingleAutocompleteChoiceType[];
|
currencyCodes?: SingleAutocompleteChoiceType[];
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
@ -40,12 +41,12 @@ export interface ChannelDetailsPageProps {
|
||||||
countries: CountryFragment[];
|
countries: CountryFragment[];
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
onSubmit: (data: FormData) => void;
|
onSubmit: (data: FormData) => SubmitPromise<TErrors[]>;
|
||||||
updateChannelStatus?: () => void;
|
updateChannelStatus?: () => void;
|
||||||
searchShippingZones: (query: string) => void;
|
searchShippingZones: (query: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
const ChannelDetailsPage = function<TErrors>({
|
||||||
channel,
|
channel,
|
||||||
currencyCodes,
|
currencyCodes,
|
||||||
disabled,
|
disabled,
|
||||||
|
@ -61,7 +62,7 @@ export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
||||||
fetchMoreShippingZones,
|
fetchMoreShippingZones,
|
||||||
countries,
|
countries,
|
||||||
channelShippingZones = []
|
channelShippingZones = []
|
||||||
}) => {
|
}: ChannelDetailsPageProps<TErrors>) {
|
||||||
const [selectedCurrencyCode, setSelectedCurrencyCode] = useState("");
|
const [selectedCurrencyCode, setSelectedCurrencyCode] = useState("");
|
||||||
const [
|
const [
|
||||||
selectedCountryDisplayName,
|
selectedCountryDisplayName,
|
||||||
|
@ -92,7 +93,7 @@ export const ChannelDetailsPage: React.FC<ChannelDetailsPageProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={onSubmit} initial={initialData}>
|
<Form confirmLeave onSubmit={onSubmit} initial={initialData}>
|
||||||
{({ change, data, hasChanged, submit, set }) => {
|
{({ change, data, hasChanged, submit, set }) => {
|
||||||
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
const handleCurrencyCodeSelect = createSingleAutocompleteSelectHandler(
|
||||||
change,
|
change,
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/ut
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||||
import currencyCodes from "currency-codes";
|
import currencyCodes from "currency-codes";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -44,6 +45,7 @@ export const ChannelCreateView = ({}) => {
|
||||||
currencyCode,
|
currencyCode,
|
||||||
...rest
|
...rest
|
||||||
}: FormData) =>
|
}: FormData) =>
|
||||||
|
extractMutationErrors(
|
||||||
createChannel({
|
createChannel({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
@ -52,7 +54,8 @@ export const ChannelCreateView = ({}) => {
|
||||||
addShippingZones: shippingZonesIdsToAdd
|
addShippingZones: shippingZonesIdsToAdd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loadMore: fetchMoreShippingZones,
|
loadMore: fetchMoreShippingZones,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/ut
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
import useShippingZonesSearch from "@saleor/searches/useShippingZonesSearch";
|
||||||
import { useChannelShippingZones } from "@saleor/shipping/queries";
|
import { useChannelShippingZones } from "@saleor/shipping/queries";
|
||||||
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
import getChannelsErrorMessage from "@saleor/utils/errors/channels";
|
||||||
|
@ -105,6 +106,7 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
shippingZonesIdsToAdd,
|
shippingZonesIdsToAdd,
|
||||||
defaultCountry
|
defaultCountry
|
||||||
}: FormData) =>
|
}: FormData) =>
|
||||||
|
extractMutationErrors(
|
||||||
updateChannel({
|
updateChannel({
|
||||||
variables: {
|
variables: {
|
||||||
id: data?.channel.id,
|
id: data?.channel.id,
|
||||||
|
@ -116,7 +118,8 @@ export const ChannelDetails: React.FC<ChannelDetailsProps> = ({
|
||||||
removeShippingZones: shippingZonesIdsToRemove
|
removeShippingZones: shippingZonesIdsToRemove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const onDeleteCompleted = (data: ChannelDelete) => {
|
const onDeleteCompleted = (data: ChannelDelete) => {
|
||||||
const errors = data.channelDelete.errors;
|
const errors = data.channelDelete.errors;
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
import { OutputData } from "@editorjs/editorjs";
|
import { OutputData } from "@editorjs/editorjs";
|
||||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||||
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||||
|
import { COLLECTION_CREATE_FORM_ID } from "@saleor/collections/views/consts";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
CommonUseFormResultWithHandlers,
|
||||||
|
FormChange,
|
||||||
|
SubmitPromise
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import useRichText from "@saleor/utils/richText/useRichText";
|
import useRichText from "@saleor/utils/richText/useRichText";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export interface CollectionCreateFormData extends MetadataFormData {
|
export interface CollectionCreateFormData extends MetadataFormData {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
|
@ -33,13 +39,10 @@ interface CollectionCreateHandlers {
|
||||||
data: Omit<ChannelCollectionData, "name" | "id">
|
data: Omit<ChannelCollectionData, "name" | "id">
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
export interface UseCollectionCreateFormResult {
|
export type UseCollectionCreateFormResult = CommonUseFormResultWithHandlers<
|
||||||
change: FormChange;
|
CollectionCreateData,
|
||||||
data: CollectionCreateData;
|
CollectionCreateHandlers
|
||||||
handlers: CollectionCreateHandlers;
|
>;
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CollectionCreateFormProps {
|
export interface CollectionCreateFormProps {
|
||||||
currentChannels: ChannelCollectionData[];
|
currentChannels: ChannelCollectionData[];
|
||||||
|
@ -48,15 +51,9 @@ export interface CollectionCreateFormProps {
|
||||||
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
onSubmit: (data: CollectionCreateData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCollectionCreateForm(
|
const getInitialData = (
|
||||||
currentChannels: ChannelCollectionData[],
|
currentChannels: ChannelCollectionData[]
|
||||||
setChannels: (data: ChannelCollectionData[]) => void,
|
): CollectionCreateFormData => ({
|
||||||
onSubmit: (data: CollectionCreateData) => SubmitPromise
|
|
||||||
): UseCollectionCreateFormResult {
|
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm<CollectionCreateFormData>({
|
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
url: null,
|
url: null,
|
||||||
value: null
|
value: null
|
||||||
|
@ -70,6 +67,34 @@ function useCollectionCreateForm(
|
||||||
seoTitle: "",
|
seoTitle: "",
|
||||||
slug: ""
|
slug: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function useCollectionCreateForm(
|
||||||
|
currentChannels: ChannelCollectionData[],
|
||||||
|
setChannels: (data: ChannelCollectionData[]) => void,
|
||||||
|
onSubmit: (data: CollectionCreateData) => SubmitPromise
|
||||||
|
): UseCollectionCreateFormResult {
|
||||||
|
const {
|
||||||
|
handleChange,
|
||||||
|
data: formData,
|
||||||
|
triggerChange,
|
||||||
|
setChanged,
|
||||||
|
hasChanged,
|
||||||
|
formId
|
||||||
|
} = useForm(getInitialData(currentChannels), undefined, {
|
||||||
|
confirmLeave: true,
|
||||||
|
formId: COLLECTION_CREATE_FORM_ID
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: null,
|
initial: null,
|
||||||
triggerChange
|
triggerChange
|
||||||
|
@ -79,15 +104,11 @@ function useCollectionCreateForm(
|
||||||
makeChangeHandler: makeMetadataChangeHandler
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||||
|
|
||||||
// Need to make it function to always have description.current up to date
|
// Need to make it function to always have description.current up to date
|
||||||
const getData = (): CollectionCreateData => ({
|
const getData = (): CollectionCreateData => ({
|
||||||
...form.data,
|
...formData,
|
||||||
description: description.current
|
description: description.current
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -97,7 +118,9 @@ function useCollectionCreateForm(
|
||||||
triggerChange
|
triggerChange
|
||||||
);
|
);
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getData());
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
|
@ -107,7 +130,7 @@ function useCollectionCreateForm(
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,20 @@ import { OutputData } from "@editorjs/editorjs";
|
||||||
import { ChannelCollectionData } from "@saleor/channels/utils";
|
import { ChannelCollectionData } from "@saleor/channels/utils";
|
||||||
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
import { CollectionDetails_collection } from "@saleor/collections/types/CollectionDetails";
|
||||||
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
import { createChannelsChangeHandler } from "@saleor/collections/utils";
|
||||||
|
import { COLLECTION_DETAILS_FORM_ID } from "@saleor/collections/views/consts";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
CommonUseFormResultWithHandlers,
|
||||||
|
FormChange
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import useRichText from "@saleor/utils/richText/useRichText";
|
import useRichText from "@saleor/utils/richText/useRichText";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export interface CollectionUpdateFormData extends MetadataFormData {
|
export interface CollectionUpdateFormData extends MetadataFormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
|
@ -32,13 +37,10 @@ interface CollectionUpdateHandlers {
|
||||||
data: Omit<ChannelCollectionData, "name" | "id">
|
data: Omit<ChannelCollectionData, "name" | "id">
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
export interface UseCollectionUpdateFormResult {
|
export type UseCollectionUpdateFormResult = CommonUseFormResultWithHandlers<
|
||||||
change: FormChange;
|
CollectionUpdateData,
|
||||||
data: CollectionUpdateData;
|
CollectionUpdateHandlers
|
||||||
handlers: CollectionUpdateHandlers;
|
>;
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CollectionUpdateFormProps {
|
export interface CollectionUpdateFormProps {
|
||||||
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
|
children: (props: UseCollectionUpdateFormResult) => React.ReactNode;
|
||||||
|
@ -48,16 +50,10 @@ export interface CollectionUpdateFormProps {
|
||||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
|
onSubmit: (data: CollectionUpdateData) => Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useCollectionUpdateForm(
|
const getInitialData = (
|
||||||
collection: CollectionDetails_collection,
|
collection: CollectionDetails_collection,
|
||||||
currentChannels: ChannelCollectionData[],
|
currentChannels: ChannelCollectionData[]
|
||||||
setChannels: (data: ChannelCollectionData[]) => void,
|
): CollectionUpdateFormData => ({
|
||||||
onSubmit: (data: CollectionUpdateData) => Promise<any[]>
|
|
||||||
): UseCollectionUpdateFormResult {
|
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm<CollectionUpdateFormData>({
|
|
||||||
backgroundImageAlt: collection?.backgroundImage?.alt || "",
|
backgroundImageAlt: collection?.backgroundImage?.alt || "",
|
||||||
channelListings: currentChannels,
|
channelListings: currentChannels,
|
||||||
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
metadata: collection?.metadata?.map(mapMetadataItemToInput),
|
||||||
|
@ -67,6 +63,35 @@ function useCollectionUpdateForm(
|
||||||
seoTitle: collection?.seoTitle || "",
|
seoTitle: collection?.seoTitle || "",
|
||||||
slug: collection?.slug || ""
|
slug: collection?.slug || ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function useCollectionUpdateForm(
|
||||||
|
collection: CollectionDetails_collection,
|
||||||
|
currentChannels: ChannelCollectionData[],
|
||||||
|
setChannels: (data: ChannelCollectionData[]) => void,
|
||||||
|
onSubmit: (data: CollectionUpdateData) => Promise<any[]>
|
||||||
|
): UseCollectionUpdateFormResult {
|
||||||
|
const {
|
||||||
|
handleChange,
|
||||||
|
data: formData,
|
||||||
|
triggerChange,
|
||||||
|
setChanged,
|
||||||
|
hasChanged,
|
||||||
|
formId
|
||||||
|
} = useForm(getInitialData(collection, currentChannels), undefined, {
|
||||||
|
confirmLeave: true,
|
||||||
|
formId: COLLECTION_DETAILS_FORM_ID
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog({
|
||||||
|
formId: COLLECTION_DETAILS_FORM_ID
|
||||||
|
});
|
||||||
|
|
||||||
const [description, changeDescription] = useRichText({
|
const [description, changeDescription] = useRichText({
|
||||||
initial: collection?.description,
|
initial: collection?.description,
|
||||||
triggerChange
|
triggerChange
|
||||||
|
@ -78,21 +103,17 @@ function useCollectionUpdateForm(
|
||||||
makeChangeHandler: makeMetadataChangeHandler
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||||
|
|
||||||
// Need to make it function to always have description.current up to date
|
// Need to make it function to always have description.current up to date
|
||||||
const getData = (): CollectionUpdateData => ({
|
const getData = (): CollectionUpdateData => ({
|
||||||
...form.data,
|
...formData,
|
||||||
description: description.current
|
description: description.current
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSubmitData = (): CollectionUpdateData => ({
|
const getSubmitData = (): CollectionUpdateData => ({
|
||||||
...getData(),
|
...getData(),
|
||||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified)
|
...getMetadata(formData, isMetadataModified, isPrivateMetadataModified)
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChannelChange = createChannelsChangeHandler(
|
const handleChannelChange = createChannelsChangeHandler(
|
||||||
|
@ -101,7 +122,9 @@ function useCollectionUpdateForm(
|
||||||
triggerChange
|
triggerChange
|
||||||
);
|
);
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(getSubmitData(), onSubmit, setChanged);
|
const submit = () => handleFormSubmit(getSubmitData());
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
|
@ -111,7 +134,7 @@ function useCollectionUpdateForm(
|
||||||
changeDescription,
|
changeDescription,
|
||||||
changeMetadata
|
changeMetadata
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import useChannels from "@saleor/hooks/useChannels";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { getMutationErrors } from "@saleor/misc";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||||
import {
|
import {
|
||||||
|
@ -30,6 +31,7 @@ import {
|
||||||
collectionListUrl,
|
collectionListUrl,
|
||||||
collectionUrl
|
collectionUrl
|
||||||
} from "../urls";
|
} from "../urls";
|
||||||
|
import { COLLECTION_CREATE_FORM_ID } from "./consts";
|
||||||
|
|
||||||
interface CollectionCreateProps {
|
interface CollectionCreateProps {
|
||||||
params: CollectionCreateUrlQueryParams;
|
params: CollectionCreateUrlQueryParams;
|
||||||
|
@ -72,7 +74,12 @@ export const CollectionCreate: React.FC<CollectionCreateProps> = ({
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(allChannels, params?.action, { closeModal, openModal });
|
} = useChannels(
|
||||||
|
allChannels,
|
||||||
|
params?.action,
|
||||||
|
{ closeModal, openModal },
|
||||||
|
{ formId: COLLECTION_CREATE_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
const [createCollection, createCollectionOpts] = useCollectionCreateMutation({
|
||||||
onCompleted: data => {
|
onCompleted: data => {
|
||||||
|
@ -130,7 +137,7 @@ export const CollectionCreate: React.FC<CollectionCreateProps> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return { id, errors: getMutationErrors(result) };
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataCreateHandler(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { getParsedDataForJsonStringField } from "@saleor/utils/richText/misc";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { getMutationState, maybe } from "../../misc";
|
import { getMutationErrors, getMutationState, maybe } from "../../misc";
|
||||||
import { productUrl } from "../../products/urls";
|
import { productUrl } from "../../products/urls";
|
||||||
import { CollectionInput } from "../../types/globalTypes";
|
import { CollectionInput } from "../../types/globalTypes";
|
||||||
import CollectionDetailsPage from "../components/CollectionDetailsPage/CollectionDetailsPage";
|
import CollectionDetailsPage from "../components/CollectionDetailsPage/CollectionDetailsPage";
|
||||||
|
@ -53,6 +53,7 @@ import {
|
||||||
CollectionUrlDialog,
|
CollectionUrlDialog,
|
||||||
CollectionUrlQueryParams
|
CollectionUrlQueryParams
|
||||||
} from "../urls";
|
} from "../urls";
|
||||||
|
import { COLLECTION_DETAILS_FORM_ID } from "./consts";
|
||||||
|
|
||||||
interface CollectionDetailsProps {
|
interface CollectionDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -196,10 +197,15 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(collectionChannelsChoices, params?.action, {
|
} = useChannels(
|
||||||
|
collectionChannelsChoices,
|
||||||
|
params?.action,
|
||||||
|
{
|
||||||
closeModal,
|
closeModal,
|
||||||
openModal
|
openModal
|
||||||
});
|
},
|
||||||
|
{ formId: COLLECTION_DETAILS_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const handleUpdate = async (formData: CollectionUpdateData) => {
|
const handleUpdate = async (formData: CollectionUpdateData) => {
|
||||||
const input: CollectionInput = {
|
const input: CollectionInput = {
|
||||||
|
@ -242,8 +248,9 @@ export const CollectionDetails: React.FC<CollectionDetailsProps> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data.collectionUpdate.errors;
|
return getMutationErrors(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
data?.collection,
|
data?.collection,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
|
|
2
src/collections/views/consts.ts
Normal file
2
src/collections/views/consts.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const COLLECTION_DETAILS_FORM_ID = Symbol();
|
||||||
|
export const COLLECTION_CREATE_FORM_ID = Symbol();
|
|
@ -9,6 +9,7 @@ interface CardTitleProps {
|
||||||
subtitle?: string | React.ReactNode;
|
subtitle?: string | React.ReactNode;
|
||||||
toolbar?: React.ReactNode;
|
toolbar?: React.ReactNode;
|
||||||
onClick?: (event: React.MouseEvent<any>) => void;
|
onClick?: (event: React.MouseEvent<any>) => void;
|
||||||
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CardTitle: React.FC<CardTitleProps> = ({
|
const CardTitle: React.FC<CardTitleProps> = ({
|
||||||
|
|
|
@ -59,18 +59,20 @@ export const EditableTableCell: React.FC<EditableTableCellProps> = props => {
|
||||||
defaultValue,
|
defaultValue,
|
||||||
focused,
|
focused,
|
||||||
InputProps,
|
InputProps,
|
||||||
value,
|
value
|
||||||
onConfirm
|
// onConfirm
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
const handleConfirm = (data: { value: string }) => {
|
// const handleConfirm = (data: { value: string }) => {
|
||||||
disable();
|
// disable();
|
||||||
onConfirm(data.value);
|
// onConfirm(data.value);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const [opened, setOpenStatus] = React.useState(focused);
|
const [opened, setOpenStatus] = React.useState(focused);
|
||||||
const { change, data } = useForm({ value }, handleConfirm);
|
const { change, data } = useForm(
|
||||||
|
{ value } /* commenting out temporarily handleConfirm */
|
||||||
|
);
|
||||||
const enable = () => setOpenStatus(true);
|
const enable = () => setOpenStatus(true);
|
||||||
const disable = () => setOpenStatus(false);
|
const disable = () => setOpenStatus(false);
|
||||||
|
|
||||||
|
|
64
src/components/Form/ExitFormDialog.tsx
Normal file
64
src/components/Form/ExitFormDialog.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
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 { exitFormPromptMessages as messages } from "./messages";
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
() => ({
|
||||||
|
container: {
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center"
|
||||||
|
},
|
||||||
|
buttonsContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{ name: "ExitFormPrompt" }
|
||||||
|
);
|
||||||
|
|
||||||
|
interface ExitFormDialogProps {
|
||||||
|
onSubmit: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
onLeave: () => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExitFormDialog: React.FC<ExitFormDialogProps> = ({
|
||||||
|
onSubmit,
|
||||||
|
onLeave,
|
||||||
|
onClose,
|
||||||
|
isOpen
|
||||||
|
}) => {
|
||||||
|
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 />
|
||||||
|
<div className={classes.buttonsContainer}>
|
||||||
|
<Button onClick={onLeave}>
|
||||||
|
{intl.formatMessage(messages.cancelButton)}
|
||||||
|
</Button>
|
||||||
|
<HorizontalSpacer />
|
||||||
|
<Button variant="contained" color="primary" onClick={onSubmit}>
|
||||||
|
{intl.formatMessage(messages.confirmButton)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExitFormDialog;
|
256
src/components/Form/ExitFormDialogProvider.tsx
Normal file
256
src/components/Form/ExitFormDialogProvider.tsx
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { useHistory } from "react-router";
|
||||||
|
import useRouter from "use-react-router";
|
||||||
|
|
||||||
|
import ExitFormDialog from "./ExitFormDialog";
|
||||||
|
|
||||||
|
export interface ExitFormDialogData {
|
||||||
|
setIsDirty: (id: symbol, isDirty: boolean) => void;
|
||||||
|
setExitDialogSubmitRef: (id: symbol, submitFn: SubmitFn) => void;
|
||||||
|
setEnableExitDialog: (value: boolean) => void;
|
||||||
|
shouldBlockNavigation: () => boolean;
|
||||||
|
setIsSubmitting: (value: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SubmitFn = (dataOrEvent?: any) => SubmitPromise<any[]>;
|
||||||
|
|
||||||
|
export type FormId = symbol;
|
||||||
|
|
||||||
|
type FormsData = Record<FormId, FormData>;
|
||||||
|
|
||||||
|
export interface WithFormId {
|
||||||
|
formId: FormId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
isDirty: boolean;
|
||||||
|
submitFn: SubmitFn | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use this context directly in components
|
||||||
|
// use useExitFormDialog hook instead
|
||||||
|
export const ExitFormDialogContext = React.createContext<ExitFormDialogData>({
|
||||||
|
setIsDirty: () => undefined,
|
||||||
|
setEnableExitDialog: () => undefined,
|
||||||
|
setExitDialogSubmitRef: () => undefined,
|
||||||
|
shouldBlockNavigation: () => false,
|
||||||
|
setIsSubmitting: () => undefined
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
isDirty: false,
|
||||||
|
showDialog: false,
|
||||||
|
blockNav: true,
|
||||||
|
navAction: null,
|
||||||
|
submit: null,
|
||||||
|
enableExitDialog: false,
|
||||||
|
isSubmitting: false,
|
||||||
|
formsData: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExitFormDialogProvider = ({ children }) => {
|
||||||
|
const history = useHistory();
|
||||||
|
const { history: routerHistory } = useRouter();
|
||||||
|
|
||||||
|
const [showDialog, setShowDialog] = useState(defaultValues.showDialog);
|
||||||
|
|
||||||
|
const isSubmitting = useRef(defaultValues.isSubmitting);
|
||||||
|
const formsData = useRef<FormsData>({});
|
||||||
|
const blockNav = useRef(defaultValues.blockNav);
|
||||||
|
const navAction = useRef(defaultValues.navAction);
|
||||||
|
const enableExitDialog = useRef(defaultValues.enableExitDialog);
|
||||||
|
const currentPath = useRef(window.location.pathname);
|
||||||
|
|
||||||
|
const setIsSubmitting = (value: boolean) => {
|
||||||
|
setEnableExitDialog(!value);
|
||||||
|
isSubmitting.current = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setEnableExitDialog = (value: boolean) => {
|
||||||
|
// dialog should never be toggled to enabled during form submission
|
||||||
|
if (isSubmitting.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enableExitDialog.current = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setDefaultFormsData = () => {
|
||||||
|
formsData.current = defaultValues.formsData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentPath = (newPath: string) => {
|
||||||
|
currentPath.current = newPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFormData = (id: symbol, newData: Partial<FormData>) => {
|
||||||
|
const updatedFormData = { ...formsData.current[id], ...newData };
|
||||||
|
|
||||||
|
formsData.current = {
|
||||||
|
...formsData.current,
|
||||||
|
[id]: updatedFormData
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set either on generic form load or on every custom form data change
|
||||||
|
// but doesn't cause re-renders
|
||||||
|
const setSubmitRef = <T extends () => SubmitPromise<any[]>>(
|
||||||
|
id: symbol,
|
||||||
|
submitFn: T
|
||||||
|
) => {
|
||||||
|
setFormData(id, { submitFn });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setIsDirty = (id: symbol, value: boolean) => {
|
||||||
|
// in case of race conitions between forms and transitions
|
||||||
|
if (!formsData.current[id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData(id, { isDirty: value });
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
setEnableExitDialog(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBlockNav = (value: boolean) => (blockNav.current = value);
|
||||||
|
|
||||||
|
const setDefaultNavAction = () =>
|
||||||
|
(navAction.current = defaultValues.navAction);
|
||||||
|
|
||||||
|
const setStateDefaultValues = () => {
|
||||||
|
setIsSubmitting(defaultValues.isSubmitting);
|
||||||
|
setDefaultFormsData();
|
||||||
|
setShowDialog(defaultValues.showDialog);
|
||||||
|
setBlockNav(defaultValues.blockNav);
|
||||||
|
setEnableExitDialog(defaultValues.enableExitDialog);
|
||||||
|
setDefaultNavAction();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormsDataValuesArray = () =>
|
||||||
|
Object.getOwnPropertySymbols(formsData.current).map(
|
||||||
|
key => formsData.current[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasAnyFormsDirty = () =>
|
||||||
|
getFormsDataValuesArray().some(({ isDirty }) => isDirty);
|
||||||
|
|
||||||
|
const shouldBlockNav = () => {
|
||||||
|
if (!enableExitDialog.current || !hasAnyFormsDirty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockNav.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOnlyQuerying = transition =>
|
||||||
|
// wee need to compare to current path and not window location
|
||||||
|
// so it works with browser back button as well
|
||||||
|
transition.pathname === currentPath.current;
|
||||||
|
|
||||||
|
const handleNavigationBlock = () => {
|
||||||
|
const unblock = history.block(transition => {
|
||||||
|
// needs to be done before the shouldBlockNav condition
|
||||||
|
// so it doesnt trigger setting default values
|
||||||
|
if (isOnlyQuerying(transition)) {
|
||||||
|
// ransition type requires this function to return either
|
||||||
|
// false | void | string where string opens up the browser prompt
|
||||||
|
// hence we return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldBlockNav()) {
|
||||||
|
navAction.current = transition;
|
||||||
|
setShowDialog(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStateDefaultValues();
|
||||||
|
setCurrentPath(transition.pathname);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return unblock;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(handleNavigationBlock, []);
|
||||||
|
|
||||||
|
const continueNavigation = () => {
|
||||||
|
setBlockNav(false);
|
||||||
|
setDefaultFormsData();
|
||||||
|
|
||||||
|
setCurrentPath(navAction.current.pathname);
|
||||||
|
// because our useNavigator navigate action may be blocked
|
||||||
|
// by exit dialog we want to avoid using it doing this transition
|
||||||
|
routerHistory.push(navAction.current.pathname);
|
||||||
|
setStateDefaultValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDirtyFormsSubmitFn = () =>
|
||||||
|
getFormsDataValuesArray()
|
||||||
|
.filter(({ isDirty }) => isDirty)
|
||||||
|
.map(({ submitFn }) => submitFn);
|
||||||
|
|
||||||
|
const hasAnySubmitFn = () =>
|
||||||
|
getFormsDataValuesArray().some(({ submitFn }) => !!submitFn);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!hasAnySubmitFn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowDialog(false);
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
const errors = await Promise.all(
|
||||||
|
getDirtyFormsSubmitFn().map(submitFn => submitFn())
|
||||||
|
);
|
||||||
|
|
||||||
|
const isError = errors.flat().some(errors => errors);
|
||||||
|
|
||||||
|
setIsSubmitting(false);
|
||||||
|
|
||||||
|
if (!isError) {
|
||||||
|
continueNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultNavAction();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLeave = () => {
|
||||||
|
continueNavigation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setDefaultNavAction();
|
||||||
|
setShowDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to prevent race conditions from places such as
|
||||||
|
// create pages with navigation on mutation completed
|
||||||
|
const shouldBlockNavigation = () => !!navAction.current;
|
||||||
|
|
||||||
|
const providerData: ExitFormDialogData = {
|
||||||
|
setIsDirty,
|
||||||
|
shouldBlockNavigation,
|
||||||
|
setEnableExitDialog,
|
||||||
|
setExitDialogSubmitRef: setSubmitRef,
|
||||||
|
setIsSubmitting
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExitFormDialogContext.Provider value={providerData}>
|
||||||
|
<ExitFormDialog
|
||||||
|
isOpen={showDialog}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onLeave={handleLeave}
|
||||||
|
onClose={handleClose}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</ExitFormDialogContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExitFormDialogProvider;
|
|
@ -1,18 +1,28 @@
|
||||||
import useForm, { SubmitPromise, UseFormResult } from "@saleor/hooks/useForm";
|
import useForm, { SubmitPromise, UseFormResult } from "@saleor/hooks/useForm";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface FormProps<T>
|
import { FormId } from "./ExitFormDialogProvider";
|
||||||
|
|
||||||
|
export interface FormProps<TData, TErrors>
|
||||||
extends Omit<React.HTMLProps<HTMLFormElement>, "onSubmit"> {
|
extends Omit<React.HTMLProps<HTMLFormElement>, "onSubmit"> {
|
||||||
children: (props: UseFormResult<T>) => React.ReactNode;
|
children: (props: UseFormResult<TData>) => React.ReactNode;
|
||||||
confirmLeave?: boolean;
|
confirmLeave?: boolean;
|
||||||
initial?: T;
|
initial?: TData;
|
||||||
resetOnSubmit?: boolean;
|
resetOnSubmit?: boolean;
|
||||||
onSubmit?: (data: T) => SubmitPromise | void;
|
onSubmit?: (data: TData) => SubmitPromise<TErrors[]> | void;
|
||||||
|
formId?: FormId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Form<T>(props: FormProps<T>) {
|
function Form<TData, Terrors>({
|
||||||
const { children, initial, resetOnSubmit, onSubmit, ...rest } = props;
|
children,
|
||||||
const renderProps = useForm(initial, onSubmit);
|
initial,
|
||||||
|
resetOnSubmit,
|
||||||
|
onSubmit,
|
||||||
|
confirmLeave = false,
|
||||||
|
formId,
|
||||||
|
...rest
|
||||||
|
}: FormProps<TData, Terrors>) {
|
||||||
|
const renderProps = useForm(initial, onSubmit, { confirmLeave, formId });
|
||||||
|
|
||||||
function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) {
|
function handleSubmit(event?: React.FormEvent<any>, cb?: () => void) {
|
||||||
const { reset, submit } = renderProps;
|
const { reset, submit } = renderProps;
|
||||||
|
|
21
src/components/Form/messages.ts
Normal file
21
src/components/Form/messages.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { defineMessages } from "react-intl";
|
||||||
|
|
||||||
|
export const exitFormPromptMessages = defineMessages({
|
||||||
|
title: {
|
||||||
|
defaultMessage: "Are you sure you want to leave?",
|
||||||
|
description: "ExitFormPrompt title"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
defaultMessage:
|
||||||
|
"You have unsaved changes on this view. What would you like to do with them?",
|
||||||
|
description: "ExitFormPrompt description"
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
defaultMessage: "leave without saving",
|
||||||
|
description: "ExitFormPrompt cancel button"
|
||||||
|
},
|
||||||
|
confirmButton: {
|
||||||
|
defaultMessage: "save & continue",
|
||||||
|
description: "ExitFormPrompt confirm button"
|
||||||
|
}
|
||||||
|
});
|
40
src/components/Form/useExitFormDialog.ts
Normal file
40
src/components/Form/useExitFormDialog.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { useContext, useRef } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ExitFormDialogContext,
|
||||||
|
ExitFormDialogData,
|
||||||
|
SubmitFn,
|
||||||
|
WithFormId
|
||||||
|
} from "./ExitFormDialogProvider";
|
||||||
|
|
||||||
|
export interface UseExitFormDialogResult
|
||||||
|
extends Pick<
|
||||||
|
ExitFormDialogData,
|
||||||
|
"setEnableExitDialog" | "shouldBlockNavigation" | "setIsSubmitting"
|
||||||
|
>,
|
||||||
|
WithFormId {
|
||||||
|
setIsDirty: (isDirty: boolean) => void;
|
||||||
|
setExitDialogSubmitRef: (submitFn: SubmitFn) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseExitFormDialogProps {
|
||||||
|
formId: symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useExitFormDialog = (
|
||||||
|
{ formId }: UseExitFormDialogProps = { formId: undefined }
|
||||||
|
): UseExitFormDialogResult => {
|
||||||
|
const id = useRef(formId || Symbol()).current;
|
||||||
|
|
||||||
|
const { setIsDirty, setExitDialogSubmitRef, ...rest } = useContext(
|
||||||
|
ExitFormDialogContext
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
formId: id,
|
||||||
|
setIsDirty: (value: boolean) => setIsDirty(id, value),
|
||||||
|
setExitDialogSubmitRef: (submitFn: SubmitFn) =>
|
||||||
|
setExitDialogSubmitRef(id, submitFn)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,23 +0,0 @@
|
||||||
import { Tooltip } from "@material-ui/core";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface ButtonTooltipDecoratorProps {
|
|
||||||
tooltip?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ButtonTooltipDecorator: React.FC<ButtonTooltipDecoratorProps> = ({
|
|
||||||
tooltip,
|
|
||||||
children
|
|
||||||
}) => {
|
|
||||||
if (tooltip) {
|
|
||||||
return (
|
|
||||||
<Tooltip title={tooltip} placement="top">
|
|
||||||
<span>{children}</span>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
};
|
|
||||||
ButtonTooltipDecorator.displayName = "ButtonTooltipDecorator";
|
|
||||||
export default ButtonTooltipDecorator;
|
|
|
@ -6,9 +6,11 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
|
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
|
||||||
import useAddressValidation from "@saleor/hooks/useAddressValidation";
|
import useAddressValidation from "@saleor/hooks/useAddressValidation";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import { AddressInput } from "@saleor/types/globalTypes";
|
import { AddressInput } from "@saleor/types/globalTypes";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import { mapCountriesToChoices } from "@saleor/utils/maps";
|
import { mapCountriesToChoices } from "@saleor/utils/maps";
|
||||||
|
@ -56,7 +58,7 @@ export interface CustomerCreatePageProps {
|
||||||
errors: AccountErrorFragment[];
|
errors: AccountErrorFragment[];
|
||||||
saveButtonBar: ConfirmButtonTransitionState;
|
saveButtonBar: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: CustomerCreatePageSubmitData) => void;
|
onSubmit: (data: CustomerCreatePageSubmitData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
|
const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
|
||||||
|
@ -117,20 +119,22 @@ const CustomerCreatePage: React.FC<CustomerCreatePageProps> = ({
|
||||||
.some(field => field !== "");
|
.some(field => field !== "");
|
||||||
|
|
||||||
if (areAddressInputFieldsModified) {
|
if (areAddressInputFieldsModified) {
|
||||||
handleSubmitWithAddress(formData);
|
return handleSubmitWithAddress(formData);
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
return extractMutationErrors(
|
||||||
onSubmit({
|
onSubmit({
|
||||||
address: null,
|
address: null,
|
||||||
customerFirstName: formData.customerFirstName,
|
customerFirstName: formData.customerFirstName,
|
||||||
customerLastName: formData.customerLastName,
|
customerLastName: formData.customerLastName,
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
note: formData.note
|
note: formData.note
|
||||||
});
|
})
|
||||||
}
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={handleSubmit} confirmLeave>
|
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit }) => {
|
||||||
const handleCountrySelect = createSingleAutocompleteSelectHandler(
|
const handleCountrySelect = createSingleAutocompleteSelectHandler(
|
||||||
change,
|
change,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Metadata from "@saleor/components/Metadata/Metadata";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
import { MetadataFormData } from "@saleor/components/Metadata/types";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
|
import { UpdateCustomer_customerUpdate_errors } from "@saleor/customers/types/UpdateCustomer";
|
||||||
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
|
import { AccountErrorFragment } from "@saleor/fragments/types/AccountErrorFragment";
|
||||||
import CustomerGiftCardsCard from "@saleor/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCard";
|
import CustomerGiftCardsCard from "@saleor/giftCards/components/GiftCardCustomerCard/CustomerGiftCardsCard";
|
||||||
import { SubmitPromise } from "@saleor/hooks/useForm";
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
@ -39,7 +40,9 @@ export interface CustomerDetailsPageProps {
|
||||||
errors: AccountErrorFragment[];
|
errors: AccountErrorFragment[];
|
||||||
saveButtonBar: ConfirmButtonTransitionState;
|
saveButtonBar: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: CustomerDetailsPageFormData) => SubmitPromise;
|
onSubmit: (
|
||||||
|
data: CustomerDetailsPageFormData
|
||||||
|
) => SubmitPromise<UpdateCustomer_customerUpdate_errors[]>;
|
||||||
onViewAllOrdersClick: () => void;
|
onViewAllOrdersClick: () => void;
|
||||||
onRowClick: (id: string) => void;
|
onRowClick: (id: string) => void;
|
||||||
onAddressManageClick: () => void;
|
onAddressManageClick: () => void;
|
||||||
|
@ -75,7 +78,7 @@ const CustomerDetailsPage: React.FC<CustomerDetailsPageProps> = ({
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit }) => {
|
||||||
const changeMetadata = makeMetadataChangeHandler(change);
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe } from "../../misc";
|
import { extractMutationErrors, maybe } from "../../misc";
|
||||||
import CustomerCreatePage from "../components/CustomerCreatePage";
|
import CustomerCreatePage from "../components/CustomerCreatePage";
|
||||||
import { TypedCreateCustomerMutation } from "../mutations";
|
import { TypedCreateCustomerMutation } from "../mutations";
|
||||||
import { TypedCustomerCreateDataQuery } from "../queries";
|
import { TypedCustomerCreateDataQuery } from "../queries";
|
||||||
|
@ -27,11 +27,29 @@ export const CustomerCreate: React.FC<{}> = () => {
|
||||||
navigate(customerUrl(data.customerCreate.user.id));
|
navigate(customerUrl(data.customerCreate.user.id));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedCustomerCreateDataQuery displayLoader>
|
<TypedCustomerCreateDataQuery displayLoader>
|
||||||
{({ data, loading }) => (
|
{({ data, loading }) => (
|
||||||
<TypedCreateCustomerMutation onCompleted={handleCreateCustomerSuccess}>
|
<TypedCreateCustomerMutation onCompleted={handleCreateCustomerSuccess}>
|
||||||
{(createCustomer, createCustomerOpts) => (
|
{(createCustomer, createCustomerOpts) => {
|
||||||
|
const handleSubmit = formData =>
|
||||||
|
extractMutationErrors(
|
||||||
|
createCustomer({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
defaultBillingAddress: formData.address,
|
||||||
|
defaultShippingAddress: formData.address,
|
||||||
|
email: formData.email,
|
||||||
|
firstName: formData.customerFirstName,
|
||||||
|
lastName: formData.customerLastName,
|
||||||
|
note: formData.note
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle
|
<WindowTitle
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
@ -45,23 +63,11 @@ export const CustomerCreate: React.FC<{}> = () => {
|
||||||
errors={createCustomerOpts.data?.customerCreate.errors || []}
|
errors={createCustomerOpts.data?.customerCreate.errors || []}
|
||||||
saveButtonBar={createCustomerOpts.status}
|
saveButtonBar={createCustomerOpts.status}
|
||||||
onBack={() => navigate(customerListUrl())}
|
onBack={() => navigate(customerListUrl())}
|
||||||
onSubmit={formData => {
|
onSubmit={handleSubmit}
|
||||||
createCustomer({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
defaultBillingAddress: formData.address,
|
|
||||||
defaultShippingAddress: formData.address,
|
|
||||||
email: formData.email,
|
|
||||||
firstName: formData.customerFirstName,
|
|
||||||
lastName: formData.customerLastName,
|
|
||||||
note: formData.note
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
);
|
||||||
|
}}
|
||||||
</TypedCreateCustomerMutation>
|
</TypedCreateCustomerMutation>
|
||||||
)}
|
)}
|
||||||
</TypedCustomerCreateDataQuery>
|
</TypedCustomerCreateDataQuery>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ActionDialog from "@saleor/components/ActionDialog";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
|
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
import { extractMutationErrors, getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import { MutationResultAdditionalProps } from "@saleor/types";
|
import { MutationResultAdditionalProps } from "@saleor/types";
|
||||||
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
import {
|
import {
|
||||||
|
@ -69,8 +69,9 @@ export const CustomerDetailsContent: React.FC<CustomerDetailsContentProps> = ({
|
||||||
const [updateMetadata] = useMetadataUpdate({});
|
const [updateMetadata] = useMetadataUpdate({});
|
||||||
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
const [updatePrivateMetadata] = usePrivateMetadataUpdate({});
|
||||||
|
|
||||||
const updateData = async (data: CustomerDetailsPageFormData) => {
|
const updateData = async (data: CustomerDetailsPageFormData) =>
|
||||||
const result = await updateCustomer({
|
extractMutationErrors(
|
||||||
|
updateCustomer({
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
id,
|
||||||
input: {
|
input: {
|
||||||
|
@ -81,10 +82,8 @@ export const CustomerDetailsContent: React.FC<CustomerDetailsContentProps> = ({
|
||||||
note: data.note
|
note: data.note
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return result.data.customerUpdate.errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
user,
|
user,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
import Hr from "@saleor/components/Hr";
|
import Hr from "@saleor/components/Hr";
|
||||||
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
import ResponsiveTable from "@saleor/components/ResponsiveTable";
|
||||||
import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo";
|
import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
|
import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
|
||||||
import { filter } from "fuzzaldrin";
|
import { filter } from "fuzzaldrin";
|
||||||
|
@ -37,7 +38,7 @@ export interface DiscountCountrySelectDialogProps {
|
||||||
initial: string[];
|
initial: string[];
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm: (data: FormData) => void;
|
onConfirm: (data: FormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DiscountCountrySelectDialog: React.FC<DiscountCountrySelectDialogProps> = props => {
|
const DiscountCountrySelectDialog: React.FC<DiscountCountrySelectDialogProps> = props => {
|
||||||
|
@ -90,7 +91,9 @@ const DiscountCountrySelectDialog: React.FC<DiscountCountrySelectDialogProps> =
|
||||||
<TextField
|
<TextField
|
||||||
name="query"
|
name="query"
|
||||||
value={data.query}
|
value={data.query}
|
||||||
onChange={event => change(event, () => fetch(data.query))}
|
onChange={event =>
|
||||||
|
change(event /* TO BE CHECKED: () => fetch(data.query)*/)
|
||||||
|
}
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
defaultMessage: "Filter Countries",
|
defaultMessage: "Filter Countries",
|
||||||
description: "search box label"
|
description: "search box label"
|
||||||
|
|
|
@ -8,7 +8,9 @@ import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import { createSaleChannelsChangeHandler } from "@saleor/discounts/handlers";
|
import { createSaleChannelsChangeHandler } from "@saleor/discounts/handlers";
|
||||||
|
import { SALE_CREATE_FORM_ID } from "@saleor/discounts/views/SaleCreate/consts";
|
||||||
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -47,7 +49,7 @@ export interface SaleCreatePageProps {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onChannelsChange: (data: ChannelSaleFormData[]) => void;
|
onChannelsChange: (data: ChannelSaleFormData[]) => void;
|
||||||
openChannelsModal: () => void;
|
openChannelsModal: () => void;
|
||||||
onSubmit: (data: FormData) => void;
|
onSubmit: (data: FormData) => SubmitPromise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
||||||
|
@ -79,8 +81,14 @@ const SaleCreatePage: React.FC<SaleCreatePageProps> = ({
|
||||||
metadata: [],
|
metadata: [],
|
||||||
privateMetadata: []
|
privateMetadata: []
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form
|
||||||
|
confirmLeave
|
||||||
|
initial={initialForm}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
formId={SALE_CREATE_FORM_ID}
|
||||||
|
>
|
||||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
{({ change, data, hasChanged, submit, triggerChange }) => {
|
||||||
const handleChannelChange = createSaleChannelsChangeHandler(
|
const handleChannelChange = createSaleChannelsChangeHandler(
|
||||||
data.channelListings,
|
data.channelListings,
|
||||||
|
|
|
@ -9,7 +9,9 @@ import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import { Tab, TabContainer } from "@saleor/components/Tab";
|
import { Tab, TabContainer } from "@saleor/components/Tab";
|
||||||
import { createSaleChannelsChangeHandler } from "@saleor/discounts/handlers";
|
import { createSaleChannelsChangeHandler } from "@saleor/discounts/handlers";
|
||||||
|
import { SALE_UPDATE_FORM_ID } from "@saleor/discounts/views/SaleDetails/types";
|
||||||
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -88,7 +90,7 @@ export interface SaleDetailsPageProps
|
||||||
onVariantUnassign: (id: string) => void;
|
onVariantUnassign: (id: string) => void;
|
||||||
onVariantClick: (productId: string, variantId: string) => () => void;
|
onVariantClick: (productId: string, variantId: string) => () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
onSubmit: (data: SaleDetailsPageFormData) => void;
|
onSubmit: (data: SaleDetailsPageFormData) => SubmitPromise<any[]>;
|
||||||
onTabClick: (index: SaleDetailsPageTab) => void;
|
onTabClick: (index: SaleDetailsPageTab) => void;
|
||||||
onChannelsChange: (data: ChannelSaleFormData[]) => void;
|
onChannelsChange: (data: ChannelSaleFormData[]) => void;
|
||||||
openChannelsModal: () => void;
|
openChannelsModal: () => void;
|
||||||
|
@ -157,7 +159,12 @@ const SaleDetailsPage: React.FC<SaleDetailsPageProps> = ({
|
||||||
privateMetadata: sale?.privateMetadata.map(mapMetadataItemToInput)
|
privateMetadata: sale?.privateMetadata.map(mapMetadataItemToInput)
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form
|
||||||
|
confirmLeave
|
||||||
|
initial={initialForm}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
formId={SALE_UPDATE_FORM_ID}
|
||||||
|
>
|
||||||
{({ change, data, hasChanged, submit, triggerChange }) => {
|
{({ change, data, hasChanged, submit, triggerChange }) => {
|
||||||
const handleChannelChange = createSaleChannelsChangeHandler(
|
const handleChannelChange = createSaleChannelsChangeHandler(
|
||||||
data.channelListings,
|
data.channelListings,
|
||||||
|
|
|
@ -4,14 +4,16 @@ import ChannelsAvailabilityCard from "@saleor/components/ChannelsAvailabilityCar
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import Form from "@saleor/components/Form";
|
import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
import Metadata, { MetadataFormData } from "@saleor/components/Metadata";
|
import Metadata from "@saleor/components/Metadata";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import {
|
import {
|
||||||
createChannelsChangeHandler,
|
createChannelsChangeHandler,
|
||||||
createDiscountTypeChangeHandler
|
createDiscountTypeChangeHandler
|
||||||
} from "@saleor/discounts/handlers";
|
} from "@saleor/discounts/handlers";
|
||||||
|
import { VOUCHER_CREATE_FORM_ID } from "@saleor/discounts/views/VoucherCreate/types";
|
||||||
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
import { DiscountErrorFragment } from "@saleor/fragments/types/DiscountErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -23,30 +25,14 @@ import { useIntl } from "react-intl";
|
||||||
import { PermissionEnum, VoucherTypeEnum } from "../../../types/globalTypes";
|
import { PermissionEnum, VoucherTypeEnum } from "../../../types/globalTypes";
|
||||||
import { DiscountTypeEnum, RequirementsPicker } from "../../types";
|
import { DiscountTypeEnum, RequirementsPicker } from "../../types";
|
||||||
import VoucherDates from "../VoucherDates";
|
import VoucherDates from "../VoucherDates";
|
||||||
|
import { VoucherDetailsPageFormData } from "../VoucherDetailsPage";
|
||||||
import VoucherInfo from "../VoucherInfo";
|
import VoucherInfo from "../VoucherInfo";
|
||||||
import VoucherLimits from "../VoucherLimits";
|
import VoucherLimits from "../VoucherLimits";
|
||||||
import VoucherRequirements from "../VoucherRequirements";
|
import VoucherRequirements from "../VoucherRequirements";
|
||||||
import VoucherTypes from "../VoucherTypes";
|
import VoucherTypes from "../VoucherTypes";
|
||||||
import VoucherValue from "../VoucherValue";
|
import VoucherValue from "../VoucherValue";
|
||||||
|
|
||||||
export interface FormData extends MetadataFormData {
|
export interface FormData extends VoucherDetailsPageFormData {
|
||||||
applyOncePerCustomer: boolean;
|
|
||||||
applyOncePerOrder: boolean;
|
|
||||||
onlyForStaff: boolean;
|
|
||||||
channelListings: ChannelVoucherData[];
|
|
||||||
code: string;
|
|
||||||
discountType: DiscountTypeEnum;
|
|
||||||
endDate: string;
|
|
||||||
endTime: string;
|
|
||||||
hasEndDate: boolean;
|
|
||||||
hasUsageLimit: boolean;
|
|
||||||
minCheckoutItemsQuantity: string;
|
|
||||||
requirementsPicker: RequirementsPicker;
|
|
||||||
startDate: string;
|
|
||||||
startTime: string;
|
|
||||||
type: VoucherTypeEnum;
|
|
||||||
usageLimit: number;
|
|
||||||
used: number;
|
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +46,7 @@ export interface VoucherCreatePageProps {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onChannelsChange: (data: ChannelVoucherData[]) => void;
|
onChannelsChange: (data: ChannelVoucherData[]) => void;
|
||||||
openChannelsModal: () => void;
|
openChannelsModal: () => void;
|
||||||
onSubmit: (data: FormData) => void;
|
onSubmit: (data: FormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
|
@ -104,7 +90,12 @@ const VoucherCreatePage: React.FC<VoucherCreatePageProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form
|
||||||
|
confirmLeave
|
||||||
|
initial={initialForm}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
formId={VOUCHER_CREATE_FORM_ID}
|
||||||
|
>
|
||||||
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
||||||
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
||||||
change
|
change
|
||||||
|
|
|
@ -192,7 +192,7 @@ const VoucherDetailsPage: React.FC<VoucherDetailsPageProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit}>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
{({ change, data, hasChanged, submit, triggerChange, set }) => {
|
||||||
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
const handleDiscountTypeChange = createDiscountTypeChangeHandler(
|
||||||
change
|
change
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { SALE_CREATE_FORM_ID } from "./consts";
|
||||||
import { createHandler } from "./handlers";
|
import { createHandler } from "./handlers";
|
||||||
|
|
||||||
interface SaleCreateProps {
|
interface SaleCreateProps {
|
||||||
|
@ -63,7 +64,12 @@ export const SaleCreateView: React.FC<SaleCreateProps> = ({ params }) => {
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(allChannels, params?.action, { closeModal, openModal });
|
} = useChannels(
|
||||||
|
allChannels,
|
||||||
|
params?.action,
|
||||||
|
{ closeModal, openModal },
|
||||||
|
{ formId: SALE_CREATE_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const [updateChannels, updateChannelsOpts] = useSaleChannelListingUpdate({});
|
const [updateChannels, updateChannelsOpts] = useSaleChannelListingUpdate({});
|
||||||
|
|
||||||
|
|
1
src/discounts/views/SaleCreate/consts.ts
Normal file
1
src/discounts/views/SaleCreate/consts.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const SALE_CREATE_FORM_ID = Symbol();
|
|
@ -8,7 +8,11 @@ import {
|
||||||
SaleCreate,
|
SaleCreate,
|
||||||
SaleCreateVariables
|
SaleCreateVariables
|
||||||
} from "@saleor/discounts/types/SaleCreate";
|
} from "@saleor/discounts/types/SaleCreate";
|
||||||
import { joinDateTime } from "@saleor/misc";
|
import {
|
||||||
|
extractMutationErrors,
|
||||||
|
getMutationErrors,
|
||||||
|
joinDateTime
|
||||||
|
} from "@saleor/misc";
|
||||||
import { decimal } from "@saleor/misc";
|
import { decimal } from "@saleor/misc";
|
||||||
import { DiscountValueTypeEnum, SaleType } from "@saleor/types/globalTypes";
|
import { DiscountValueTypeEnum, SaleType } from "@saleor/types/globalTypes";
|
||||||
import { MutationFetchResult } from "react-apollo";
|
import { MutationFetchResult } from "react-apollo";
|
||||||
|
@ -40,14 +44,25 @@ export function createHandler(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.data.saleCreate.errors.length) {
|
const errors = getMutationErrors(response);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return { errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateChannelsErrors = await extractMutationErrors(
|
||||||
updateChannels({
|
updateChannels({
|
||||||
variables: getSaleChannelsVariables(
|
variables: getSaleChannelsVariables(
|
||||||
response.data.saleCreate.sale.id,
|
response.data.saleCreate.sale.id,
|
||||||
formData
|
formData
|
||||||
)
|
)
|
||||||
});
|
})
|
||||||
return response.data.saleCreate.sale.id;
|
);
|
||||||
|
|
||||||
|
if (updateChannelsErrors.length > 0) {
|
||||||
|
return { errors: updateChannelsErrors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { id: response.data.saleCreate.sale.id };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { createUpdateHandler } from "./handlers";
|
import { createUpdateHandler } from "./handlers";
|
||||||
import { messages } from "./messages";
|
import { messages } from "./messages";
|
||||||
|
import { SALE_UPDATE_FORM_ID } from "./types";
|
||||||
|
|
||||||
interface SaleDetailsProps {
|
interface SaleDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -144,10 +145,15 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(saleChannelsChoices, params?.action, {
|
} = useChannels(
|
||||||
|
saleChannelsChoices,
|
||||||
|
params?.action,
|
||||||
|
{
|
||||||
closeModal,
|
closeModal,
|
||||||
openModal
|
openModal
|
||||||
});
|
},
|
||||||
|
{ formId: SALE_UPDATE_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const [selectedChannel] = useLocalStorage("salesListChannel", "");
|
const [selectedChannel] = useLocalStorage("salesListChannel", "");
|
||||||
|
|
||||||
|
|
1
src/discounts/views/SaleDetails/types.ts
Normal file
1
src/discounts/views/SaleDetails/types.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const SALE_UPDATE_FORM_ID = Symbol();
|
|
@ -32,6 +32,7 @@ import {
|
||||||
voucherUrl
|
voucherUrl
|
||||||
} from "../../urls";
|
} from "../../urls";
|
||||||
import { createHandler } from "./handlers";
|
import { createHandler } from "./handlers";
|
||||||
|
import { VOUCHER_CREATE_FORM_ID } from "./types";
|
||||||
|
|
||||||
interface VoucherCreateProps {
|
interface VoucherCreateProps {
|
||||||
params: VoucherCreateUrlQueryParams;
|
params: VoucherCreateUrlQueryParams;
|
||||||
|
@ -65,7 +66,12 @@ export const VoucherCreateView: React.FC<VoucherCreateProps> = ({ params }) => {
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(allChannels, params?.action, { closeModal, openModal });
|
} = useChannels(
|
||||||
|
allChannels,
|
||||||
|
params?.action,
|
||||||
|
{ closeModal, openModal },
|
||||||
|
{ formId: VOUCHER_CREATE_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const [updateChannels, updateChannelsOpts] = useVoucherChannelListingUpdate(
|
const [updateChannels, updateChannelsOpts] = useVoucherChannelListingUpdate(
|
||||||
{}
|
{}
|
||||||
|
|
|
@ -9,7 +9,11 @@ import {
|
||||||
VoucherCreate,
|
VoucherCreate,
|
||||||
VoucherCreateVariables
|
VoucherCreateVariables
|
||||||
} from "@saleor/discounts/types/VoucherCreate";
|
} from "@saleor/discounts/types/VoucherCreate";
|
||||||
import { joinDateTime } from "@saleor/misc";
|
import {
|
||||||
|
extractMutationErrors,
|
||||||
|
getMutationErrors,
|
||||||
|
joinDateTime
|
||||||
|
} from "@saleor/misc";
|
||||||
import {
|
import {
|
||||||
DiscountValueTypeEnum,
|
DiscountValueTypeEnum,
|
||||||
VoucherTypeEnum
|
VoucherTypeEnum
|
||||||
|
@ -53,15 +57,26 @@ export function createHandler(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.data.voucherCreate.errors.length) {
|
const errors = getMutationErrors(response);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return { errors };
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelsUpdateErrors = await extractMutationErrors(
|
||||||
updateChannels({
|
updateChannels({
|
||||||
variables: getChannelsVariables(
|
variables: getChannelsVariables(
|
||||||
response.data.voucherCreate.voucher.id,
|
response.data.voucherCreate.voucher.id,
|
||||||
formData,
|
formData,
|
||||||
formData.channelListings
|
formData.channelListings
|
||||||
)
|
)
|
||||||
});
|
})
|
||||||
return response.data.voucherCreate.voucher.id;
|
);
|
||||||
|
|
||||||
|
if (channelsUpdateErrors.length > 0) {
|
||||||
|
return { errors: channelsUpdateErrors };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { id: response.data.voucherCreate.voucher.id };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
1
src/discounts/views/VoucherCreate/types.ts
Normal file
1
src/discounts/views/VoucherCreate/types.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const VOUCHER_CREATE_FORM_ID = Symbol();
|
|
@ -63,6 +63,7 @@ import { collectionUrl } from "../../../collections/urls";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import { productUrl } from "../../../products/urls";
|
import { productUrl } from "../../../products/urls";
|
||||||
import { createUpdateHandler } from "./handlers";
|
import { createUpdateHandler } from "./handlers";
|
||||||
|
import { VOUCHER_UPDATE_FORM_ID } from "./types";
|
||||||
|
|
||||||
interface VoucherDetailsProps {
|
interface VoucherDetailsProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -152,10 +153,15 @@ export const VoucherDetails: React.FC<VoucherDetailsProps> = ({
|
||||||
isChannelsModalOpen,
|
isChannelsModalOpen,
|
||||||
setCurrentChannels,
|
setCurrentChannels,
|
||||||
toggleAllChannels
|
toggleAllChannels
|
||||||
} = useChannels(voucherChannelsChoices, params?.action, {
|
} = useChannels(
|
||||||
|
voucherChannelsChoices,
|
||||||
|
params?.action,
|
||||||
|
{
|
||||||
closeModal,
|
closeModal,
|
||||||
openModal
|
openModal
|
||||||
});
|
},
|
||||||
|
{ formId: VOUCHER_UPDATE_FORM_ID }
|
||||||
|
);
|
||||||
|
|
||||||
const [updateChannels, updateChannelsOpts] = useVoucherChannelListingUpdate(
|
const [updateChannels, updateChannelsOpts] = useVoucherChannelListingUpdate(
|
||||||
{}
|
{}
|
||||||
|
|
1
src/discounts/views/VoucherDetails/types.ts
Normal file
1
src/discounts/views/VoucherDetails/types.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const VOUCHER_UPDATE_FORM_ID = Symbol();
|
|
@ -3,10 +3,10 @@ import { GiftCardError } from "@saleor/fragments/types/GiftCardError";
|
||||||
import { giftCardUpdateFormMessages } from "@saleor/giftCards/GiftCardsList/messages";
|
import { giftCardUpdateFormMessages } from "@saleor/giftCards/GiftCardsList/messages";
|
||||||
import { MutationResultWithOpts } from "@saleor/hooks/makeMutation";
|
import { MutationResultWithOpts } from "@saleor/hooks/makeMutation";
|
||||||
import useForm, { FormChange, UseFormResult } from "@saleor/hooks/useForm";
|
import useForm, { FormChange, UseFormResult } from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/utils";
|
import { getDefaultNotifierSuccessErrorData } from "@saleor/hooks/useNotifier/utils";
|
||||||
import { getFormErrors } from "@saleor/utils/errors";
|
import { getFormErrors } from "@saleor/utils/errors";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
|
||||||
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||||
|
@ -131,9 +131,22 @@ const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
|
||||||
return result?.data?.giftCardUpdate?.errors;
|
return result?.data?.giftCardUpdate?.errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formProps = useForm<GiftCardUpdateFormData>(getInitialData());
|
const formProps = useForm(getInitialData());
|
||||||
|
|
||||||
const { data, change, setChanged, hasChanged } = formProps;
|
const { data, change, setChanged, hasChanged, formId } = formProps;
|
||||||
|
|
||||||
|
const handleSubmit = createMetadataUpdateHandler(
|
||||||
|
giftCard,
|
||||||
|
submit,
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit: handleSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isMetadataModified,
|
isMetadataModified,
|
||||||
|
@ -148,15 +161,7 @@ const GiftCardUpdateFormProvider: React.FC<GiftCardUpdateFormProviderProps> = ({
|
||||||
...getMetadata(data, isMetadataModified, isPrivateMetadataModified)
|
...getMetadata(data, isMetadataModified, isPrivateMetadataModified)
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataUpdateHandler(
|
const formSubmit = () => handleFormSubmit(submitData);
|
||||||
giftCard,
|
|
||||||
submit,
|
|
||||||
variables => updateMetadata({ variables }),
|
|
||||||
variables => updatePrivateMetadata({ variables })
|
|
||||||
);
|
|
||||||
|
|
||||||
const formSubmit = () =>
|
|
||||||
handleFormSubmit(submitData, handleSubmit, setChanged);
|
|
||||||
|
|
||||||
const formErrors = getFormErrors(
|
const formErrors = getFormErrors(
|
||||||
["tags", "expiryDate"],
|
["tags", "expiryDate"],
|
||||||
|
|
|
@ -11,7 +11,9 @@ import { useState } from "react";
|
||||||
|
|
||||||
interface UseAddressValidation<TInput, TOutput> {
|
interface UseAddressValidation<TInput, TOutput> {
|
||||||
errors: AccountErrorFragment[];
|
errors: AccountErrorFragment[];
|
||||||
submit: (data: TInput & AddressTypeInput) => TOutput;
|
submit: (
|
||||||
|
data: TInput & AddressTypeInput
|
||||||
|
) => TOutput | Promise<AccountErrorFragment[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAddressValidation<TInput, TOutput>(
|
function useAddressValidation<TInput, TOutput>(
|
||||||
|
@ -42,7 +44,10 @@ function useAddressValidation<TInput, TOutput>(
|
||||||
);
|
);
|
||||||
return onSubmit(transformFormToAddressInput(data));
|
return onSubmit(transformFormToAddressInput(data));
|
||||||
} catch {
|
} catch {
|
||||||
setValidationErrors(add(countryRequiredError, validationErrors));
|
const errors = add(countryRequiredError, validationErrors);
|
||||||
|
setValidationErrors(errors);
|
||||||
|
// since every onSubmit must return Promise<error>
|
||||||
|
return Promise.resolve(errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,10 +20,15 @@ describe("useChannels", () => {
|
||||||
it("properly toggles channels", () => {
|
it("properly toggles channels", () => {
|
||||||
// Given
|
// Given
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useChannels(channels, "", {
|
useChannels(
|
||||||
|
channels,
|
||||||
|
"",
|
||||||
|
{
|
||||||
closeModal: jest.fn,
|
closeModal: jest.fn,
|
||||||
openModal: jest.fn
|
openModal: jest.fn
|
||||||
})
|
},
|
||||||
|
{ formId: Symbol() }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
@ -42,10 +47,15 @@ describe("useChannels", () => {
|
||||||
it("properly removes channels", () => {
|
it("properly removes channels", () => {
|
||||||
// Given
|
// Given
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useChannels(channels, "", {
|
useChannels(
|
||||||
|
channels,
|
||||||
|
"",
|
||||||
|
{
|
||||||
closeModal: jest.fn,
|
closeModal: jest.fn,
|
||||||
openModal: jest.fn
|
openModal: jest.fn
|
||||||
})
|
},
|
||||||
|
{ formId: Symbol() }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
@ -68,10 +78,15 @@ describe("useChannels", () => {
|
||||||
it("doesn't not save changes if closed without confirm", () => {
|
it("doesn't not save changes if closed without confirm", () => {
|
||||||
// Given
|
// Given
|
||||||
const { result } = renderHook(() =>
|
const { result } = renderHook(() =>
|
||||||
useChannels(channels, "", {
|
useChannels(
|
||||||
|
channels,
|
||||||
|
"",
|
||||||
|
{
|
||||||
closeModal: jest.fn,
|
closeModal: jest.fn,
|
||||||
openModal: jest.fn
|
openModal: jest.fn
|
||||||
})
|
},
|
||||||
|
{ formId: Symbol() }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { ChannelsAction } from "@saleor/channels/urls";
|
import { ChannelsAction } from "@saleor/channels/urls";
|
||||||
import { Channel } from "@saleor/channels/utils";
|
import { Channel } from "@saleor/channels/utils";
|
||||||
|
import { WithFormId } from "@saleor/components/Form/ExitFormDialogProvider";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import useListActions from "@saleor/hooks/useListActions";
|
import useListActions from "@saleor/hooks/useListActions";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
|
||||||
|
@ -11,8 +13,15 @@ interface Modal {
|
||||||
function useChannels<T extends Channel, A>(
|
function useChannels<T extends Channel, A>(
|
||||||
channels: T[],
|
channels: T[],
|
||||||
action: A | ChannelsAction,
|
action: A | ChannelsAction,
|
||||||
{ closeModal, openModal }: Modal
|
{ closeModal, openModal }: Modal,
|
||||||
|
opts: WithFormId
|
||||||
) {
|
) {
|
||||||
|
const { formId } = opts;
|
||||||
|
|
||||||
|
const { setIsDirty } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
const [currentChannels, setCurrentChannels] = useStateFromProps(channels);
|
const [currentChannels, setCurrentChannels] = useStateFromProps(channels);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -34,6 +43,11 @@ function useChannels<T extends Channel, A>(
|
||||||
(channel, nextChannel) => channel.name.localeCompare(nextChannel.name)
|
(channel, nextChannel) => channel.name.localeCompare(nextChannel.name)
|
||||||
);
|
);
|
||||||
setCurrentChannels(sortedChannelListElements);
|
setCurrentChannels(sortedChannelListElements);
|
||||||
|
|
||||||
|
// hack so channels also update exit form dalog provider
|
||||||
|
// despite not setting page's form data "changed" prop
|
||||||
|
setIsDirty(true);
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
import { FormId } from "@saleor/components/Form/ExitFormDialogProvider";
|
||||||
|
import {
|
||||||
|
useExitFormDialog,
|
||||||
|
UseExitFormDialogResult
|
||||||
|
} from "@saleor/components/Form/useExitFormDialog";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import { toggle } from "@saleor/utils/lists";
|
import { toggle } from "@saleor/utils/lists";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
import omit from "lodash/omit";
|
import omit from "lodash/omit";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import useStateFromProps from "./useStateFromProps";
|
import useStateFromProps from "./useStateFromProps";
|
||||||
|
@ -12,7 +18,7 @@ export interface ChangeEvent<TData = any> {
|
||||||
value: TData;
|
value: TData;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export type SubmitPromise = Promise<any[]>;
|
export type SubmitPromise<TData = any> = Promise<TData>;
|
||||||
|
|
||||||
export type FormChange = (event: ChangeEvent, cb?: () => void) => void;
|
export type FormChange = (event: ChangeEvent, cb?: () => void) => void;
|
||||||
|
|
||||||
|
@ -20,19 +26,35 @@ export type FormErrors<T> = {
|
||||||
[field in keyof T]?: string | React.ReactNode;
|
[field in keyof T]?: string | React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UseFormResult<T> {
|
export interface UseFormOpts {
|
||||||
change: FormChange;
|
confirmLeave: boolean;
|
||||||
data: T;
|
formId?: FormId;
|
||||||
hasChanged: boolean;
|
}
|
||||||
|
|
||||||
|
export interface UseFormResult<TData>
|
||||||
|
extends CommonUseFormResult<TData>,
|
||||||
|
Pick<UseExitFormDialogResult, "formId"> {
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
set: (data: Partial<T>) => void;
|
set: (data: Partial<TData>) => void;
|
||||||
submit: () => void;
|
|
||||||
triggerChange: () => void;
|
triggerChange: () => void;
|
||||||
toggleValue: FormChange;
|
|
||||||
errors: FormErrors<T>;
|
|
||||||
setChanged: (value: boolean) => void;
|
setChanged: (value: boolean) => void;
|
||||||
setError: (name: keyof T, error: string | React.ReactNode) => void;
|
handleChange: FormChange;
|
||||||
clearErrors: (name?: keyof T | Array<keyof T>) => void;
|
toggleValue: FormChange;
|
||||||
|
errors: FormErrors<TData>;
|
||||||
|
setError: (name: keyof TData, error: string | React.ReactNode) => void;
|
||||||
|
clearErrors: (name?: keyof TData | Array<keyof TData>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonUseFormResult<TData> {
|
||||||
|
data: TData;
|
||||||
|
change: FormChange;
|
||||||
|
hasChanged: boolean;
|
||||||
|
submit: (dataOrEvent?: any) => SubmitPromise<any[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonUseFormResultWithHandlers<TData, THandlers>
|
||||||
|
extends CommonUseFormResult<TData> {
|
||||||
|
handlers: THandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormData = Record<string, any | any[]>;
|
type FormData = Record<string, any | any[]>;
|
||||||
|
@ -60,25 +82,61 @@ function handleRefresh<T extends FormData>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function useForm<T extends FormData>(
|
function useForm<T extends FormData, TErrors>(
|
||||||
initial: T,
|
initialData: T,
|
||||||
onSubmit?: (data: T) => SubmitPromise | void
|
onSubmit?: (data: T) => SubmitPromise<TErrors[]> | void,
|
||||||
|
opts: UseFormOpts = { confirmLeave: false, formId: undefined }
|
||||||
): UseFormResult<T> {
|
): UseFormResult<T> {
|
||||||
|
const { confirmLeave, formId: propsFormId } = opts;
|
||||||
const [hasChanged, setChanged] = useState(false);
|
const [hasChanged, setChanged] = useState(false);
|
||||||
const [errors, setErrors] = useState<FormErrors<T>>({});
|
const [errors, setErrors] = useState<FormErrors<T>>({});
|
||||||
const [data, setData] = useStateFromProps(initial, {
|
const [data, setData] = useStateFromProps(initialData, {
|
||||||
mergeFunc: merge,
|
mergeFunc: merge,
|
||||||
onRefresh: newData => handleRefresh(data, newData, setChanged)
|
onRefresh: newData => handleRefresh(data, newData, handleSetChanged)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
setIsDirty: setIsFormDirtyInExitDialog,
|
||||||
|
setExitDialogSubmitRef,
|
||||||
|
setEnableExitDialog,
|
||||||
|
formId
|
||||||
|
} = useExitFormDialog({ formId: propsFormId });
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSetChanged = (value: boolean = true) => {
|
||||||
|
setChanged(value);
|
||||||
|
|
||||||
|
if (confirmLeave) {
|
||||||
|
setIsFormDirtyInExitDialog(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setExitDialogData = () => {
|
||||||
|
setEnableExitDialog(true);
|
||||||
|
|
||||||
|
if (!onSubmit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExitDialogSubmitRef(submit);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(setExitDialogData, [onSubmit]);
|
||||||
|
|
||||||
function toggleValue(event: ChangeEvent, cb?: () => void) {
|
function toggleValue(event: ChangeEvent, cb?: () => void) {
|
||||||
const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
const field = data[name as keyof T];
|
const field = data[name as keyof T];
|
||||||
|
|
||||||
if (Array.isArray(field)) {
|
if (Array.isArray(field)) {
|
||||||
if (!hasChanged) {
|
if (!hasChanged) {
|
||||||
setChanged(true);
|
handleSetChanged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setData({
|
setData({
|
||||||
...data,
|
...data,
|
||||||
[name]: toggle(value, field, isEqual)
|
[name]: toggle(value, field, isEqual)
|
||||||
|
@ -90,6 +148,11 @@ function useForm<T extends FormData>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleChange: FormChange = event => {
|
||||||
|
change(event);
|
||||||
|
handleSetChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
function change(event: ChangeEvent) {
|
function change(event: ChangeEvent) {
|
||||||
const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
|
|
||||||
|
@ -98,7 +161,7 @@ function useForm<T extends FormData>(
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (data[name] !== value) {
|
if (data[name] !== value) {
|
||||||
setChanged(true);
|
handleSetChanged(true);
|
||||||
}
|
}
|
||||||
setData(data => ({
|
setData(data => ({
|
||||||
...data,
|
...data,
|
||||||
|
@ -108,7 +171,7 @@ function useForm<T extends FormData>(
|
||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
setData(initial);
|
setData(initialData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(newData: Partial<T>, setHasChanged = true) {
|
function set(newData: Partial<T>, setHasChanged = true) {
|
||||||
|
@ -116,23 +179,15 @@ function useForm<T extends FormData>(
|
||||||
...data,
|
...data,
|
||||||
...newData
|
...newData
|
||||||
}));
|
}));
|
||||||
setChanged(setHasChanged);
|
handleSetChanged(setHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (typeof onSubmit === "function" && !Object.keys(errors).length) {
|
if (typeof onSubmit === "function" && !Object.keys(errors).length) {
|
||||||
const result = onSubmit(data);
|
const result = handleFormSubmit(data);
|
||||||
if (result) {
|
|
||||||
const errors = await result;
|
|
||||||
if (errors?.length === 0) {
|
|
||||||
setChanged(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerChange() {
|
return result;
|
||||||
setChanged(true);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setError = (field: keyof T, error: string | React.ReactNode) =>
|
const setError = (field: keyof T, error: string | React.ReactNode) =>
|
||||||
|
@ -149,6 +204,7 @@ function useForm<T extends FormData>(
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
formId,
|
||||||
setError,
|
setError,
|
||||||
errors,
|
errors,
|
||||||
change,
|
change,
|
||||||
|
@ -156,11 +212,12 @@ function useForm<T extends FormData>(
|
||||||
data,
|
data,
|
||||||
hasChanged,
|
hasChanged,
|
||||||
reset,
|
reset,
|
||||||
setChanged,
|
|
||||||
set,
|
set,
|
||||||
submit,
|
submit,
|
||||||
toggleValue,
|
toggleValue,
|
||||||
triggerChange
|
handleChange,
|
||||||
|
triggerChange: handleSetChanged,
|
||||||
|
setChanged: handleSetChanged
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
45
src/hooks/useHandleFormSubmit.ts
Normal file
45
src/hooks/useHandleFormSubmit.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { FormId } from "@saleor/components/Form/ExitFormDialogProvider";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
|
|
||||||
|
interface UseHandleFormSubmitProps<TData, TErrors> {
|
||||||
|
formId?: FormId;
|
||||||
|
onSubmit: (data: TData) => SubmitPromise<TErrors[]> | void;
|
||||||
|
setChanged: (changed: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useHandleFormSubmit<TData, TErrors>({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
}: UseHandleFormSubmitProps<TData, TErrors>) {
|
||||||
|
const { setIsSubmitting } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleFormSubmit(data: TData): Promise<TErrors[]> {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
const result = onSubmit(data);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = await result;
|
||||||
|
|
||||||
|
if (errors?.length === 0) {
|
||||||
|
setChanged(false);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(false);
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleFormSubmit;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useHandleFormSubmit;
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ExitFormDialogContext } from "@saleor/components/Form/ExitFormDialogProvider";
|
||||||
|
import { useContext } from "react";
|
||||||
import useRouter from "use-react-router";
|
import useRouter from "use-react-router";
|
||||||
|
|
||||||
export type UseNavigatorResult = (
|
export type UseNavigatorResult = (
|
||||||
|
@ -14,11 +16,18 @@ function useNavigator(): UseNavigatorResult {
|
||||||
history
|
history
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
|
|
||||||
|
const { shouldBlockNavigation } = useContext(ExitFormDialogContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
url: string,
|
url: string,
|
||||||
{ replace = false, preserveQs = false, resetScroll = false } = {}
|
{ replace = false, preserveQs = false, resetScroll = false } = {}
|
||||||
) => {
|
) => {
|
||||||
|
if (shouldBlockNavigation()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const targetUrl = preserveQs ? url + search : url;
|
const targetUrl = preserveQs ? url + search : url;
|
||||||
|
|
||||||
if (replace) {
|
if (replace) {
|
||||||
history.replace(targetUrl);
|
history.replace(targetUrl);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import useAppChannel, {
|
||||||
AppChannelProvider
|
AppChannelProvider
|
||||||
} from "./components/AppLayout/AppChannelContext";
|
} from "./components/AppLayout/AppChannelContext";
|
||||||
import { DateProvider } from "./components/Date";
|
import { DateProvider } from "./components/Date";
|
||||||
|
import ExitFormDialogProvider from "./components/Form/ExitFormDialogProvider";
|
||||||
import { LocaleProvider } from "./components/Locale";
|
import { LocaleProvider } from "./components/Locale";
|
||||||
import MessageManagerProvider from "./components/messages";
|
import MessageManagerProvider from "./components/messages";
|
||||||
import { ShopProvider } from "./components/Shop";
|
import { ShopProvider } from "./components/Shop";
|
||||||
|
@ -137,7 +138,9 @@ const App: React.FC = () => (
|
||||||
<ShopProvider>
|
<ShopProvider>
|
||||||
<AppChannelProvider>
|
<AppChannelProvider>
|
||||||
<ExternalAppProvider>
|
<ExternalAppProvider>
|
||||||
|
<ExitFormDialogProvider>
|
||||||
<Routes />
|
<Routes />
|
||||||
|
</ExitFormDialogProvider>
|
||||||
</ExternalAppProvider>
|
</ExternalAppProvider>
|
||||||
</AppChannelProvider>
|
</AppChannelProvider>
|
||||||
</ShopProvider>
|
</ShopProvider>
|
||||||
|
|
51
src/misc.ts
51
src/misc.ts
|
@ -1,7 +1,11 @@
|
||||||
import { ConfirmButtonTransitionState, ThemeType } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState, ThemeType } from "@saleor/macaw-ui";
|
||||||
import uniqBy from "lodash/uniqBy";
|
import uniqBy from "lodash/uniqBy";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { MutationFunction, MutationResult } from "react-apollo";
|
import {
|
||||||
|
MutationFetchResult,
|
||||||
|
MutationFunction,
|
||||||
|
MutationResult
|
||||||
|
} from "react-apollo";
|
||||||
import { IntlShape } from "react-intl";
|
import { IntlShape } from "react-intl";
|
||||||
|
|
||||||
import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField";
|
import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField";
|
||||||
|
@ -237,21 +241,46 @@ export function getMutationState(
|
||||||
return "default";
|
return "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SaleorMutationResult {
|
export interface SaleorMutationResult {
|
||||||
errors?: UserError[];
|
errors?: any[];
|
||||||
}
|
}
|
||||||
export function getMutationErrors<
|
|
||||||
TData extends Record<string, SaleorMutationResult>
|
type InferPromiseResult<T> = T extends Promise<infer V> ? V : never;
|
||||||
>(data: TData): UserError[] {
|
|
||||||
return Object.values(data).reduce(
|
export const extractMutationErrors = async <
|
||||||
(acc: UserError[], mut) => [...acc, ...maybe(() => mut.errors, [])],
|
TData extends InferPromiseResult<TPromise>,
|
||||||
[]
|
TPromise extends Promise<MutationFetchResult<TData>>,
|
||||||
);
|
TErrors extends ReturnType<typeof getMutationErrors>
|
||||||
|
>(
|
||||||
|
submitPromise: TPromise
|
||||||
|
): Promise<TErrors> => {
|
||||||
|
const result = await submitPromise;
|
||||||
|
|
||||||
|
const e = getMutationErrors(result);
|
||||||
|
|
||||||
|
return e as TErrors;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMutationErrors = <
|
||||||
|
T extends MutationFetchResult<any>,
|
||||||
|
TData extends T["data"],
|
||||||
|
TErrors extends TData[keyof TData]["errors"]
|
||||||
|
>(
|
||||||
|
result: T
|
||||||
|
): TErrors[] => {
|
||||||
|
if (!result?.data) {
|
||||||
|
return [] as TErrors;
|
||||||
}
|
}
|
||||||
|
return Object.values(result.data).reduce(
|
||||||
|
(acc: TErrors[], mut: TData) => [...acc, ...(mut.errors || [])],
|
||||||
|
[] as TErrors[]
|
||||||
|
) as TErrors;
|
||||||
|
};
|
||||||
|
|
||||||
export function getMutationStatus<
|
export function getMutationStatus<
|
||||||
TData extends Record<string, SaleorMutationResult | any>
|
TData extends Record<string, SaleorMutationResult | any>
|
||||||
>(opts: MutationResult<TData>): ConfirmButtonTransitionState {
|
>(opts: MutationResult<TData>): ConfirmButtonTransitionState {
|
||||||
const errors = opts.data ? getMutationErrors(opts.data) : [];
|
const errors = getMutationErrors(opts);
|
||||||
|
|
||||||
return getMutationState(opts.called, opts.loading, errors);
|
return getMutationState(opts.called, opts.loading, errors);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ const MenuDetailsPage: React.FC<MenuDetailsPageProps> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initialForm} onSubmit={handleSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => (
|
{({ change, data, hasChanged, submit }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Backlink onClick={onBack}>
|
<Backlink onClick={onBack}>
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { categoryUrl } from "../../../categories/urls";
|
import { categoryUrl } from "../../../categories/urls";
|
||||||
import { collectionUrl } from "../../../collections/urls";
|
import { collectionUrl } from "../../../collections/urls";
|
||||||
import { maybe } from "../../../misc";
|
import { extractMutationErrors, maybe } from "../../../misc";
|
||||||
import { pageUrl } from "../../../pages/urls";
|
import { pageUrl } from "../../../pages/urls";
|
||||||
import MenuDetailsPage, {
|
import MenuDetailsPage, {
|
||||||
MenuDetailsSubmitData
|
MenuDetailsSubmitData
|
||||||
|
@ -191,7 +191,11 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
open={params.action === "remove"}
|
open={params.action === "remove"}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
confirmButtonState={menuDeleteOpts.status}
|
confirmButtonState={menuDeleteOpts.status}
|
||||||
onConfirm={() => menuDelete({ variables: { id } })}
|
onConfirm={() =>
|
||||||
|
extractMutationErrors(
|
||||||
|
menuDelete({ variables: { id } })
|
||||||
|
)
|
||||||
|
}
|
||||||
variant="delete"
|
variant="delete"
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
defaultMessage: "Delete Menu",
|
defaultMessage: "Delete Menu",
|
||||||
|
@ -227,7 +231,9 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
input: getMenuItemCreateInputData(id, data)
|
input: getMenuItemCreateInputData(id, data)
|
||||||
};
|
};
|
||||||
|
|
||||||
menuItemCreate({ variables });
|
extractMutationErrors(
|
||||||
|
menuItemCreate({ variables })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -268,7 +274,9 @@ const MenuDetails: React.FC<MenuDetailsProps> = ({ id, params }) => {
|
||||||
input: getMenuItemInputData(data)
|
input: getMenuItemInputData(data)
|
||||||
};
|
};
|
||||||
|
|
||||||
menuItemUpdate({ variables });
|
extractMutationErrors(
|
||||||
|
menuItemUpdate({ variables })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuItem = maybe(() =>
|
const menuItem = maybe(() =>
|
||||||
|
|
|
@ -150,7 +150,7 @@ const OrderCustomer: React.FC<OrderCustomerProps> = props => {
|
||||||
{user === undefined ? (
|
{user === undefined ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : isInEditMode && canEditCustomer ? (
|
) : isInEditMode && canEditCustomer ? (
|
||||||
<Form initial={{ query: "" }}>
|
<Form confirmLeave initial={{ query: "" }}>
|
||||||
{({ change, data }) => {
|
{({ change, data }) => {
|
||||||
const handleChange = (event: React.ChangeEvent<any>) => {
|
const handleChange = (event: React.ChangeEvent<any>) => {
|
||||||
change(event);
|
change(event);
|
||||||
|
|
|
@ -58,7 +58,7 @@ export interface OrderCustomerAddressesEditDialogProps {
|
||||||
defaultShippingAddress?: CustomerAddresses_user_defaultShippingAddress;
|
defaultShippingAddress?: CustomerAddresses_user_defaultShippingAddress;
|
||||||
defaultBillingAddress?: CustomerAddresses_user_defaultBillingAddress;
|
defaultBillingAddress?: CustomerAddresses_user_defaultBillingAddress;
|
||||||
onClose();
|
onClose();
|
||||||
onConfirm(data: OrderCustomerAddressesEditDialogOutput): SubmitPromise;
|
onConfirm(data: OrderCustomerAddressesEditDialogOutput): SubmitPromise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSearchState: OrderCustomerSearchAddressState = {
|
const defaultSearchState: OrderCustomerSearchAddressState = {
|
||||||
|
@ -133,8 +133,13 @@ const OrderCustomerAddressesEditDialog: React.FC<OrderCustomerAddressesEditDialo
|
||||||
const adressesInput = handleAddressesSubmit(data);
|
const adressesInput = handleAddressesSubmit(data);
|
||||||
|
|
||||||
if (adressesInput.shippingAddress && adressesInput.billingAddress) {
|
if (adressesInput.shippingAddress && adressesInput.billingAddress) {
|
||||||
onConfirm(adressesInput);
|
onConfirm(adressesInput as OrderCustomerAddressesEditDialogOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve([
|
||||||
|
...shippingValidationErrors,
|
||||||
|
...billingValidationErrors
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const countryChoices = mapCountriesToChoices(countries);
|
const countryChoices = mapCountriesToChoices(countries);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||||
import { AddressTypeInput } from "@saleor/customers/types";
|
import { AddressTypeInput } from "@saleor/customers/types";
|
||||||
import {
|
import {
|
||||||
|
@ -5,9 +6,14 @@ import {
|
||||||
CustomerAddresses_user_defaultBillingAddress,
|
CustomerAddresses_user_defaultBillingAddress,
|
||||||
CustomerAddresses_user_defaultShippingAddress
|
CustomerAddresses_user_defaultShippingAddress
|
||||||
} from "@saleor/customers/types/CustomerAddresses";
|
} from "@saleor/customers/types/CustomerAddresses";
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
|
CommonUseFormResultWithHandlers,
|
||||||
|
FormChange,
|
||||||
|
SubmitPromise
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
export enum AddressInputOptionEnum {
|
export enum AddressInputOptionEnum {
|
||||||
CUSTOMER_ADDRESS = "customerAddress",
|
CUSTOMER_ADDRESS = "customerAddress",
|
||||||
|
@ -43,12 +49,12 @@ export interface OrderCustomerAddressesEditHandlers {
|
||||||
selectBillingCountry: FormChange;
|
selectBillingCountry: FormChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseOrderCustomerAddressesEditFormResult {
|
interface UseOrderCustomerAddressesEditFormResult
|
||||||
submit: (event: React.FormEvent<any>) => void;
|
extends CommonUseFormResultWithHandlers<
|
||||||
change: FormChange;
|
OrderCustomerAddressesEditData,
|
||||||
hasChanged: boolean;
|
OrderCustomerAddressesEditHandlers
|
||||||
data: OrderCustomerAddressesEditData;
|
> {
|
||||||
handlers: OrderCustomerAddressesEditHandlers;
|
submit: (event: React.FormEvent<any>) => SubmitPromise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseOrderCustomerAddressesEditFormOpts {
|
interface UseOrderCustomerAddressesEditFormOpts {
|
||||||
|
@ -61,14 +67,9 @@ export interface OrderCustomerAddressesEditFormProps
|
||||||
extends UseOrderCustomerAddressesEditFormOpts {
|
extends UseOrderCustomerAddressesEditFormOpts {
|
||||||
children: (props: UseOrderCustomerAddressesEditFormResult) => React.ReactNode;
|
children: (props: UseOrderCustomerAddressesEditFormResult) => React.ReactNode;
|
||||||
initial?: Partial<OrderCustomerAddressesEditFormData>;
|
initial?: Partial<OrderCustomerAddressesEditFormData>;
|
||||||
onSubmit: (data: OrderCustomerAddressesEditData) => void;
|
onSubmit: (data: OrderCustomerAddressesEditData) => SubmitPromise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useOrderCustomerAddressesEditForm(
|
|
||||||
initial: Partial<OrderCustomerAddressesEditFormData>,
|
|
||||||
onSubmit: (data: OrderCustomerAddressesEditData) => void,
|
|
||||||
opts: UseOrderCustomerAddressesEditFormOpts
|
|
||||||
): UseOrderCustomerAddressesEditFormResult {
|
|
||||||
const initialAddress: AddressTypeInput = {
|
const initialAddress: AddressTypeInput = {
|
||||||
city: "",
|
city: "",
|
||||||
country: "",
|
country: "",
|
||||||
|
@ -76,7 +77,10 @@ function useOrderCustomerAddressesEditForm(
|
||||||
postalCode: "",
|
postalCode: "",
|
||||||
streetAddress1: ""
|
streetAddress1: ""
|
||||||
};
|
};
|
||||||
const defaultInitialFormData: OrderCustomerAddressesEditFormData = {
|
|
||||||
|
const getDefaultInitialFormData = (
|
||||||
|
opts: UseOrderCustomerAddressesEditFormOpts
|
||||||
|
): OrderCustomerAddressesEditFormData => ({
|
||||||
billingSameAsShipping: true,
|
billingSameAsShipping: true,
|
||||||
shippingAddressInputOption: AddressInputOptionEnum.CUSTOMER_ADDRESS,
|
shippingAddressInputOption: AddressInputOptionEnum.CUSTOMER_ADDRESS,
|
||||||
billingAddressInputOption: AddressInputOptionEnum.CUSTOMER_ADDRESS,
|
billingAddressInputOption: AddressInputOptionEnum.CUSTOMER_ADDRESS,
|
||||||
|
@ -84,15 +88,25 @@ function useOrderCustomerAddressesEditForm(
|
||||||
customerBillingAddress: opts.defaultBillingAddress,
|
customerBillingAddress: opts.defaultBillingAddress,
|
||||||
shippingAddress: initialAddress,
|
shippingAddress: initialAddress,
|
||||||
billingAddress: initialAddress
|
billingAddress: initialAddress
|
||||||
};
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
...initial,
|
|
||||||
...defaultInitialFormData
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [changed, setChanged] = useState(false);
|
function useOrderCustomerAddressesEditForm(
|
||||||
const triggerChange = () => setChanged(true);
|
initial: Partial<OrderCustomerAddressesEditFormData>,
|
||||||
|
onSubmit: (data: OrderCustomerAddressesEditData) => SubmitPromise<any[]>,
|
||||||
|
opts: UseOrderCustomerAddressesEditFormOpts
|
||||||
|
): UseOrderCustomerAddressesEditFormResult {
|
||||||
|
const {
|
||||||
|
handleChange,
|
||||||
|
hasChanged,
|
||||||
|
change,
|
||||||
|
data: formData,
|
||||||
|
setChanged
|
||||||
|
} = useForm({
|
||||||
|
...initial,
|
||||||
|
...getDefaultInitialFormData(opts)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog();
|
||||||
|
|
||||||
const [shippingCountryDisplayName, setShippingCountryDisplayName] = useState(
|
const [shippingCountryDisplayName, setShippingCountryDisplayName] = useState(
|
||||||
""
|
""
|
||||||
|
@ -101,19 +115,15 @@ function useOrderCustomerAddressesEditForm(
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const handleFormAddressChange = (
|
const handleFormAddressChange = (
|
||||||
event: React.ChangeEvent<any>,
|
event: React.ChangeEvent<any>,
|
||||||
addressType: "shippingAddress" | "billingAddress"
|
addressType: "shippingAddress" | "billingAddress"
|
||||||
) =>
|
) =>
|
||||||
form.change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
name: addressType,
|
name: addressType,
|
||||||
value: {
|
value: {
|
||||||
...form.data[addressType],
|
...formData[addressType],
|
||||||
[event.target.name]: event.target.value
|
[event.target.name]: event.target.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +132,7 @@ function useOrderCustomerAddressesEditForm(
|
||||||
customerAddress: CustomerAddresses_user_addresses,
|
customerAddress: CustomerAddresses_user_addresses,
|
||||||
addressType: "customerShippingAddress" | "customerBillingAddress"
|
addressType: "customerShippingAddress" | "customerBillingAddress"
|
||||||
) =>
|
) =>
|
||||||
form.change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
name: addressType,
|
name: addressType,
|
||||||
value: customerAddress
|
value: customerAddress
|
||||||
|
@ -130,11 +140,11 @@ function useOrderCustomerAddressesEditForm(
|
||||||
});
|
});
|
||||||
const handleShippingCountrySelect = createSingleAutocompleteSelectHandler(
|
const handleShippingCountrySelect = createSingleAutocompleteSelectHandler(
|
||||||
event =>
|
event =>
|
||||||
form.change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
name: "shippingAddress",
|
name: "shippingAddress",
|
||||||
value: {
|
value: {
|
||||||
...form.data.shippingAddress,
|
...formData.shippingAddress,
|
||||||
[event.target.name]: event.target.value
|
[event.target.name]: event.target.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,11 +154,11 @@ function useOrderCustomerAddressesEditForm(
|
||||||
);
|
);
|
||||||
const handleBillingCountrySelect = createSingleAutocompleteSelectHandler(
|
const handleBillingCountrySelect = createSingleAutocompleteSelectHandler(
|
||||||
event =>
|
event =>
|
||||||
form.change({
|
change({
|
||||||
target: {
|
target: {
|
||||||
name: "billingAddress",
|
name: "billingAddress",
|
||||||
value: {
|
value: {
|
||||||
...form.data.billingAddress,
|
...formData.billingAddress,
|
||||||
[event.target.name]: event.target.value
|
[event.target.name]: event.target.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,21 +168,30 @@ function useOrderCustomerAddressesEditForm(
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
...form.data,
|
...formData,
|
||||||
shippingCountryDisplayName,
|
shippingCountryDisplayName,
|
||||||
billingCountryDisplayName
|
billingCountryDisplayName
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = () => handleFormSubmit(data);
|
||||||
|
|
||||||
const submit = (event: React.FormEvent<any>) => {
|
const submit = (event: React.FormEvent<any>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return onSubmit(data);
|
return handleSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [handleSubmit]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
submit,
|
submit,
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
data,
|
data,
|
||||||
handlers: {
|
handlers: {
|
||||||
changeCustomerAddress: handleCustomerAddressChange,
|
changeCustomerAddress: handleCustomerAddressChange,
|
||||||
|
|
|
@ -23,8 +23,8 @@ import { useStyles } from "./styles";
|
||||||
|
|
||||||
export interface OrderCustomerChangeDialogProps {
|
export interface OrderCustomerChangeDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
onConfirm: (data: OrderCustomerChangeData) => void;
|
||||||
onClose();
|
onClose();
|
||||||
onConfirm(data: OrderCustomerChangeData): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderCustomerChangeDialog: React.FC<OrderCustomerChangeDialogProps> = props => {
|
const OrderCustomerChangeDialog: React.FC<OrderCustomerChangeDialogProps> = props => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import useForm, { FormChange } from "@saleor/hooks/useForm";
|
import useForm, { CommonUseFormResult } from "@saleor/hooks/useForm";
|
||||||
import React, { useState } from "react";
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export enum CustomerChangeActionEnum {
|
export enum CustomerChangeActionEnum {
|
||||||
KEEP_ADDRESS = "keepAddress",
|
KEEP_ADDRESS = "keepAddress",
|
||||||
|
@ -10,12 +11,9 @@ export interface OrderCustomerChangeData {
|
||||||
changeActionOption: CustomerChangeActionEnum;
|
changeActionOption: CustomerChangeActionEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseOrderCustomerChangeFormResult {
|
type UseOrderCustomerChangeFormResult = CommonUseFormResult<
|
||||||
submit: (event: React.FormEvent<any>) => void;
|
OrderCustomerChangeData
|
||||||
change: FormChange;
|
>;
|
||||||
hasChanged: boolean;
|
|
||||||
data: OrderCustomerChangeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrderCustomerChangeFormProps {
|
export interface OrderCustomerChangeFormProps {
|
||||||
children: (props: UseOrderCustomerChangeFormResult) => React.ReactNode;
|
children: (props: UseOrderCustomerChangeFormResult) => React.ReactNode;
|
||||||
|
@ -23,38 +21,37 @@ export interface OrderCustomerChangeFormProps {
|
||||||
onSubmit: (data: OrderCustomerChangeData) => void;
|
onSubmit: (data: OrderCustomerChangeData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useOrderCustomerChangeForm(
|
|
||||||
initial: Partial<OrderCustomerChangeData>,
|
|
||||||
onSubmit: (data: OrderCustomerChangeData) => void
|
|
||||||
): UseOrderCustomerChangeFormResult {
|
|
||||||
const defaultInitialFormData: OrderCustomerChangeData = {
|
const defaultInitialFormData: OrderCustomerChangeData = {
|
||||||
changeActionOption: CustomerChangeActionEnum.KEEP_ADDRESS
|
changeActionOption: CustomerChangeActionEnum.KEEP_ADDRESS
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = useForm({
|
function useOrderCustomerChangeForm(
|
||||||
|
initial: Partial<OrderCustomerChangeData> = {},
|
||||||
|
onSubmit: (data: OrderCustomerChangeData) => void
|
||||||
|
): UseOrderCustomerChangeFormResult {
|
||||||
|
const { handleChange, hasChanged, data, setChanged } = useForm({
|
||||||
...initial,
|
...initial,
|
||||||
...defaultInitialFormData
|
...defaultInitialFormData
|
||||||
});
|
});
|
||||||
|
|
||||||
const [changed, setChanged] = useState(false);
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
const triggerChange = () => setChanged(true);
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
const handleSubmit = () => handleFormSubmit(data);
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = (event: React.FormEvent<any>) => {
|
const submit = (event: React.FormEvent<any>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return onSubmit(form.data);
|
return handleSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
submit,
|
submit,
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
data: form.data
|
data
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +60,7 @@ const OrderCustomerChangeForm: React.FC<OrderCustomerChangeFormProps> = ({
|
||||||
initial,
|
initial,
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const props = useOrderCustomerChangeForm(initial || {}, onSubmit);
|
const props = useOrderCustomerChangeForm(initial, onSubmit);
|
||||||
|
|
||||||
return <form onSubmit={props.submit}>{children(props)}</form>;
|
return <form onSubmit={props.submit}>{children(props)}</form>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,7 @@ import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTr
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { defineMessages, useIntl } from "react-intl";
|
import { defineMessages, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe } from "../../../misc";
|
import { getMutationErrors, maybe } from "../../../misc";
|
||||||
import { OrderStatus } from "../../../types/globalTypes";
|
import { OrderStatus } from "../../../types/globalTypes";
|
||||||
import {
|
import {
|
||||||
OrderDetails_order,
|
OrderDetails_order,
|
||||||
|
@ -173,7 +173,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
privateMetadata
|
privateMetadata
|
||||||
});
|
});
|
||||||
resetMetadataChanged();
|
resetMetadataChanged();
|
||||||
return result;
|
return getMutationErrors(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initial: MetadataFormData = {
|
const initial: MetadataFormData = {
|
||||||
|
@ -212,7 +212,7 @@ const OrderDetailsPage: React.FC<OrderDetailsPageProps> = props => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initial} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initial} onSubmit={handleSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit }) => {
|
||||||
const changeMetadata = makeMetadataChangeHandler(change);
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Grid from "@saleor/components/Grid";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -50,7 +51,7 @@ export interface OrderDraftPageProps
|
||||||
onCustomerEdit: (data: CustomerEditData) => void;
|
onCustomerEdit: (data: CustomerEditData) => void;
|
||||||
onDraftFinalize: () => void;
|
onDraftFinalize: () => void;
|
||||||
onDraftRemove: () => void;
|
onDraftRemove: () => void;
|
||||||
onNoteAdd: (data: HistoryFormData) => void;
|
onNoteAdd: (data: HistoryFormData) => SubmitPromise<any[]>;
|
||||||
onOrderLineAdd: () => void;
|
onOrderLineAdd: () => void;
|
||||||
onOrderLineChange: (
|
onOrderLineChange: (
|
||||||
id: string,
|
id: string,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Skeleton from "@saleor/components/Skeleton";
|
||||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||||
import { ShopOrderSettingsFragment } from "@saleor/fragments/types/ShopOrderSettingsFragment";
|
import { ShopOrderSettingsFragment } from "@saleor/fragments/types/ShopOrderSettingsFragment";
|
||||||
import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment";
|
import { WarehouseFragment } from "@saleor/fragments/types/WarehouseFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
|
import useFormset, { FormsetData } from "@saleor/hooks/useFormset";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
|
@ -110,7 +111,7 @@ const useStyles = makeStyles(
|
||||||
interface OrderFulfillFormData {
|
interface OrderFulfillFormData {
|
||||||
sendInfo: boolean;
|
sendInfo: boolean;
|
||||||
}
|
}
|
||||||
interface OrderFulfillSubmitData extends OrderFulfillFormData {
|
export interface OrderFulfillSubmitData extends OrderFulfillFormData {
|
||||||
items: FormsetData<null, OrderFulfillStockInput[]>;
|
items: FormsetData<null, OrderFulfillStockInput[]>;
|
||||||
}
|
}
|
||||||
export interface OrderFulfillPageProps {
|
export interface OrderFulfillPageProps {
|
||||||
|
@ -121,7 +122,7 @@ export interface OrderFulfillPageProps {
|
||||||
warehouses: WarehouseFragment[];
|
warehouses: WarehouseFragment[];
|
||||||
shopSettings?: ShopOrderSettingsFragment;
|
shopSettings?: ShopOrderSettingsFragment;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (data: OrderFulfillSubmitData) => void;
|
onSubmit: (data: OrderFulfillSubmitData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialFormData: OrderFulfillFormData = {
|
const initialFormData: OrderFulfillFormData = {
|
||||||
|
@ -220,7 +221,7 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
|
||||||
orderNumber: order?.number
|
orderNumber: order?.number
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Form initial={initialFormData} onSubmit={handleSubmit}>
|
<Form confirmLeave initial={initialFormData} onSubmit={handleSubmit}>
|
||||||
{({ change, data, submit }) => (
|
{({ change, data, submit }) => (
|
||||||
<>
|
<>
|
||||||
<Card>
|
<Card>
|
||||||
|
|
|
@ -76,7 +76,7 @@ const OrderFulfillmentCancelDialog: React.FC<OrderFulfillmentCancelDialogProps>
|
||||||
fullWidth
|
fullWidth
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
>
|
>
|
||||||
<Form initial={{ warehouseId: null }} onSubmit={onConfirm}>
|
<Form confirmLeave initial={{ warehouseId: null }} onSubmit={onConfirm}>
|
||||||
{({ change, data: formData, submit }) => {
|
{({ change, data: formData, submit }) => {
|
||||||
const handleChange = createSingleAutocompleteSelectHandler(
|
const handleChange = createSingleAutocompleteSelectHandler(
|
||||||
change,
|
change,
|
||||||
|
|
|
@ -48,7 +48,7 @@ const OrderFulfillmentTrackingDialog: React.FC<OrderFulfillmentTrackingDialogPro
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog onClose={onClose} open={open} fullWidth maxWidth="xs">
|
<Dialog onClose={onClose} open={open} fullWidth maxWidth="xs">
|
||||||
<Form initial={{ trackingNumber }} onSubmit={onConfirm}>
|
<Form confirmLeave initial={{ trackingNumber }} onSubmit={onConfirm}>
|
||||||
{({ change, data, submit }) => (
|
{({ change, data, submit }) => (
|
||||||
<>
|
<>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TimelineEventProps,
|
TimelineEventProps,
|
||||||
TimelineNote
|
TimelineNote
|
||||||
} from "@saleor/components/Timeline";
|
} from "@saleor/components/Timeline";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { makeStyles } from "@saleor/macaw-ui";
|
import { makeStyles } from "@saleor/macaw-ui";
|
||||||
import { OrderDetails_order_events } from "@saleor/orders/types/OrderDetails";
|
import { OrderDetails_order_events } from "@saleor/orders/types/OrderDetails";
|
||||||
import {
|
import {
|
||||||
|
@ -295,7 +296,7 @@ const useStyles = makeStyles(
|
||||||
interface OrderHistoryProps {
|
interface OrderHistoryProps {
|
||||||
history: OrderDetails_order_events[];
|
history: OrderDetails_order_events[];
|
||||||
orderCurrency: string;
|
orderCurrency: string;
|
||||||
onNoteAdd: (data: FormData) => void;
|
onNoteAdd: (data: FormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderHistory: React.FC<OrderHistoryProps> = props => {
|
const OrderHistory: React.FC<OrderHistoryProps> = props => {
|
||||||
|
@ -331,7 +332,12 @@ const OrderHistory: React.FC<OrderHistoryProps> = props => {
|
||||||
<Hr />
|
<Hr />
|
||||||
{history ? (
|
{history ? (
|
||||||
<Timeline>
|
<Timeline>
|
||||||
<Form initial={{ message: "" }} onSubmit={onNoteAdd} resetOnSubmit>
|
<Form
|
||||||
|
confirmLeave
|
||||||
|
initial={{ message: "" }}
|
||||||
|
onSubmit={onNoteAdd}
|
||||||
|
resetOnSubmit
|
||||||
|
>
|
||||||
{({ change, data, reset, submit }) => (
|
{({ change, data, reset, submit }) => (
|
||||||
<TimelineAddNote
|
<TimelineAddNote
|
||||||
message={data.message}
|
message={data.message}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
|
import useForm, {
|
||||||
|
CommonUseFormResultWithHandlers,
|
||||||
|
SubmitPromise
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
import useFormset, {
|
import useFormset, {
|
||||||
FormsetChange,
|
FormsetChange,
|
||||||
FormsetData
|
FormsetData
|
||||||
} from "@saleor/hooks/useFormset";
|
} from "@saleor/hooks/useFormset";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import { OrderRefundData_order } from "@saleor/orders/types/OrderRefundData";
|
import { OrderRefundData_order } from "@saleor/orders/types/OrderRefundData";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
import React, { useEffect } from "react";
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { refundFulfilledStatuses } from "./OrderRefundPage";
|
import { refundFulfilledStatuses } from "./OrderRefundPage";
|
||||||
|
|
||||||
|
@ -40,13 +44,12 @@ export interface OrderRefundFormData extends OrderRefundData {
|
||||||
|
|
||||||
export type OrderRefundSubmitData = OrderRefundFormData;
|
export type OrderRefundSubmitData = OrderRefundFormData;
|
||||||
|
|
||||||
export interface UseOrderRefundFormResult {
|
export interface UseOrderRefundFormResult
|
||||||
change: FormChange;
|
extends CommonUseFormResultWithHandlers<
|
||||||
data: OrderRefundFormData;
|
OrderRefundFormData,
|
||||||
|
OrderRefundHandlers
|
||||||
|
> {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
handlers: OrderRefundHandlers;
|
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OrderRefundFormProps {
|
interface OrderRefundFormProps {
|
||||||
|
@ -72,10 +75,19 @@ function useOrderRefundForm(
|
||||||
defaultType: OrderRefundType,
|
defaultType: OrderRefundType,
|
||||||
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
|
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
|
||||||
): UseOrderRefundFormResult {
|
): UseOrderRefundFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
const {
|
||||||
const triggerChange = () => setChanged(true);
|
handleChange,
|
||||||
|
setChanged,
|
||||||
|
hasChanged,
|
||||||
|
triggerChange,
|
||||||
|
data: formData,
|
||||||
|
formId
|
||||||
|
} = useForm(getOrderRefundPageFormData(defaultType), undefined, {
|
||||||
|
confirmLeave: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog();
|
||||||
|
|
||||||
const form = useForm(getOrderRefundPageFormData(defaultType));
|
|
||||||
const refundedProductQuantities = useFormset<null, string>(
|
const refundedProductQuantities = useFormset<null, string>(
|
||||||
order?.lines
|
order?.lines
|
||||||
.filter(line => line.quantityToFulfill > 0)
|
.filter(line => line.quantityToFulfill > 0)
|
||||||
|
@ -103,10 +115,6 @@ function useOrderRefundForm(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const handleRefundedProductQuantityChange: FormsetChange<string> = (
|
const handleRefundedProductQuantityChange: FormsetChange<string> = (
|
||||||
id,
|
id,
|
||||||
value
|
value
|
||||||
|
@ -165,12 +173,20 @@ function useOrderRefundForm(
|
||||||
};
|
};
|
||||||
|
|
||||||
const data: OrderRefundFormData = {
|
const data: OrderRefundFormData = {
|
||||||
...form.data,
|
...formData,
|
||||||
refundedFulfilledProductQuantities: refundedFulfilledProductQuantities.data,
|
refundedFulfilledProductQuantities: refundedFulfilledProductQuantities.data,
|
||||||
refundedProductQuantities: refundedProductQuantities.data
|
refundedProductQuantities: refundedProductQuantities.data
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(data, onSubmit, setChanged);
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = () => handleFormSubmit(data);
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
const disabled = !order;
|
const disabled = !order;
|
||||||
|
|
||||||
|
@ -184,7 +200,7 @@ function useOrderRefundForm(
|
||||||
setMaximalRefundedFulfilledProductQuantities: handleMaximalRefundedFulfilledProductQuantitiesSet,
|
setMaximalRefundedFulfilledProductQuantities: handleMaximalRefundedFulfilledProductQuantitiesSet,
|
||||||
setMaximalRefundedProductQuantities: handleMaximalRefundedProductQuantitiesSet
|
setMaximalRefundedProductQuantities: handleMaximalRefundedProductQuantitiesSet
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
|
import useForm, {
|
||||||
|
CommonUseFormResultWithHandlers,
|
||||||
|
SubmitPromise
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
import useFormset, {
|
import useFormset, {
|
||||||
FormsetChange,
|
FormsetChange,
|
||||||
FormsetData
|
FormsetData
|
||||||
} from "@saleor/hooks/useFormset";
|
} from "@saleor/hooks/useFormset";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
import { OrderDetails_order } from "@saleor/orders/types/OrderDetails";
|
||||||
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
import { FulfillmentStatus } from "@saleor/types/globalTypes";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
import React, { useEffect } from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
import { OrderRefundAmountCalculationMode } from "../OrderRefundPage/form";
|
import { OrderRefundAmountCalculationMode } from "../OrderRefundPage/form";
|
||||||
import {
|
import {
|
||||||
|
@ -55,13 +59,10 @@ export interface OrderReturnFormData extends OrderReturnData {
|
||||||
|
|
||||||
export type OrderRefundSubmitData = OrderReturnFormData;
|
export type OrderRefundSubmitData = OrderReturnFormData;
|
||||||
|
|
||||||
export interface UseOrderRefundFormResult {
|
export type UseOrderRefundFormResult = CommonUseFormResultWithHandlers<
|
||||||
change: FormChange;
|
OrderReturnFormData,
|
||||||
hasChanged: boolean;
|
OrderReturnHandlers
|
||||||
data: OrderReturnFormData;
|
>;
|
||||||
handlers: OrderReturnHandlers;
|
|
||||||
submit: () => Promise<boolean>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OrderReturnProps {
|
interface OrderReturnProps {
|
||||||
children: (props: UseOrderRefundFormResult) => React.ReactNode;
|
children: (props: UseOrderRefundFormResult) => React.ReactNode;
|
||||||
|
@ -79,12 +80,18 @@ function useOrderReturnForm(
|
||||||
order: OrderDetails_order,
|
order: OrderDetails_order,
|
||||||
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
|
onSubmit: (data: OrderRefundSubmitData) => SubmitPromise
|
||||||
): UseOrderRefundFormResult {
|
): UseOrderRefundFormResult {
|
||||||
const form = useForm(getOrderRefundPageFormData());
|
const {
|
||||||
const [hasChanged, setHasChanged] = useState(false);
|
handleChange,
|
||||||
|
setChanged,
|
||||||
|
hasChanged,
|
||||||
|
data: formData,
|
||||||
|
triggerChange,
|
||||||
|
formId
|
||||||
|
} = useForm(getOrderRefundPageFormData(), undefined, {
|
||||||
|
confirmLeave: true
|
||||||
|
});
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
const { setExitDialogSubmitRef } = useExitFormDialog();
|
||||||
form.change(event, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
const unfulfiledItemsQuantites = useFormset<LineItemData, number>(
|
const unfulfiledItemsQuantites = useFormset<LineItemData, number>(
|
||||||
getOrderUnfulfilledLines(order).map(getParsedLineData({ initialValue: 0 }))
|
getOrderUnfulfilledLines(order).map(getParsedLineData({ initialValue: 0 }))
|
||||||
|
@ -215,12 +222,18 @@ function useOrderReturnForm(
|
||||||
waitingItemsQuantities: waitingItemsQuantities.data,
|
waitingItemsQuantities: waitingItemsQuantities.data,
|
||||||
itemsToBeReplaced: itemsToBeReplaced.data,
|
itemsToBeReplaced: itemsToBeReplaced.data,
|
||||||
unfulfilledItemsQuantities: unfulfiledItemsQuantites.data,
|
unfulfilledItemsQuantities: unfulfiledItemsQuantites.data,
|
||||||
...form.data
|
...formData
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(data, onSubmit, setHasChanged);
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
|
formId,
|
||||||
|
onSubmit,
|
||||||
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
const triggerChange = () => setHasChanged(true);
|
const submit = () => handleFormSubmit(data);
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
function handleHandlerChange<T>(callback: (id: string, value: T) => void) {
|
function handleHandlerChange<T>(callback: (id: string, value: T) => void) {
|
||||||
return (id: string, value: T) => {
|
return (id: string, value: T) => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { OrderSettingsFormData } from "../OrderSettingsPage/form";
|
import { OrderSettingsFormData } from "../OrderSettingsPage/types";
|
||||||
|
|
||||||
export interface OrderSettingsProps {
|
export interface OrderSettingsProps {
|
||||||
data: OrderSettingsFormData;
|
data: OrderSettingsFormData;
|
||||||
|
|
|
@ -14,7 +14,8 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import OrderFulfillmentSettings from "../OrderFulfillmentSettings";
|
import OrderFulfillmentSettings from "../OrderFulfillmentSettings";
|
||||||
import OrderSettings from "../OrderSettings/OrderSettings";
|
import OrderSettings from "../OrderSettings/OrderSettings";
|
||||||
import OrderSettingsForm, { OrderSettingsFormData } from "./form";
|
import OrderSettingsForm from "./form";
|
||||||
|
import { OrderSettingsFormData } from "./types";
|
||||||
|
|
||||||
export interface OrderSettingsPageProps {
|
export interface OrderSettingsPageProps {
|
||||||
orderSettings: OrderSettingsFragment;
|
orderSettings: OrderSettingsFragment;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { OrderSettingsFragment } from "@saleor/fragments/types/OrderSettingsFragment";
|
import { OrderSettingsFragment } from "@saleor/fragments/types/OrderSettingsFragment";
|
||||||
import { ShopOrderSettingsFragment } from "@saleor/fragments/types/ShopOrderSettingsFragment";
|
import { ShopOrderSettingsFragment } from "@saleor/fragments/types/ShopOrderSettingsFragment";
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface OrderSettingsFormData {
|
export interface OrderSettingsFormData {
|
||||||
|
@ -15,7 +15,7 @@ export interface UseOrderSettingsFormResult {
|
||||||
change: FormChange;
|
change: FormChange;
|
||||||
data: OrderSettingsFormData;
|
data: OrderSettingsFormData;
|
||||||
hasChanged: boolean;
|
hasChanged: boolean;
|
||||||
submit: () => Promise<boolean>;
|
submit: () => SubmitPromise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderSettingsFormProps {
|
export interface OrderSettingsFormProps {
|
||||||
|
@ -45,20 +45,20 @@ function useOrderSettingsForm(
|
||||||
onSubmit: (data: OrderSettingsFormData) => SubmitPromise
|
onSubmit: (data: OrderSettingsFormData) => SubmitPromise
|
||||||
): UseOrderSettingsFormResult {
|
): UseOrderSettingsFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
const [changed, setChanged] = React.useState(false);
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const form = useForm(getOrderSeettingsFormData(orderSettings, shop));
|
const { data, handleChange, formId } = useForm(
|
||||||
|
getOrderSeettingsFormData(orderSettings, shop),
|
||||||
|
undefined,
|
||||||
|
{ confirmLeave: true }
|
||||||
|
);
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
form.change(event, cb);
|
formId,
|
||||||
triggerChange();
|
onSubmit,
|
||||||
};
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
const data: OrderSettingsFormData = {
|
const submit = () => handleFormSubmit(data);
|
||||||
...form.data
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit = () => handleFormSubmit(form.data, onSubmit, setChanged);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
change: handleChange,
|
change: handleChange,
|
||||||
|
|
6
src/orders/components/OrderSettingsPage/types.ts
Normal file
6
src/orders/components/OrderSettingsPage/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface OrderSettingsFormData {
|
||||||
|
automaticallyConfirmAllNewOrders: boolean;
|
||||||
|
fulfillmentAutoApprove: boolean;
|
||||||
|
fulfillmentAllowUnpaid: boolean;
|
||||||
|
automaticallyFulfillNonShippableGiftCard: boolean;
|
||||||
|
}
|
265
src/orders/views/OrderDetails/OrderDetails.tsx
Normal file
265
src/orders/views/OrderDetails/OrderDetails.tsx
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
|
import { Task } from "@saleor/containers/BackgroundTasks/types";
|
||||||
|
import useBackgroundTask from "@saleor/hooks/useBackgroundTask";
|
||||||
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { useOrderConfirmMutation } from "@saleor/orders/mutations";
|
||||||
|
import { InvoiceRequest } from "@saleor/orders/types/InvoiceRequest";
|
||||||
|
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
||||||
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
||||||
|
import {
|
||||||
|
useMetadataUpdate,
|
||||||
|
usePrivateMetadataUpdate
|
||||||
|
} from "@saleor/utils/metadata/updateMetadata";
|
||||||
|
import React from "react";
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { JobStatusEnum, OrderStatus } from "../../../types/globalTypes";
|
||||||
|
import OrderOperations from "../../containers/OrderOperations";
|
||||||
|
import { TypedOrderDetailsQuery } from "../../queries";
|
||||||
|
import {
|
||||||
|
orderListUrl,
|
||||||
|
orderUrl,
|
||||||
|
OrderUrlDialog,
|
||||||
|
OrderUrlQueryParams
|
||||||
|
} from "../../urls";
|
||||||
|
import OrderAddressFields from "./OrderAddressFields";
|
||||||
|
import { OrderDetailsMessages } from "./OrderDetailsMessages";
|
||||||
|
import { OrderDraftDetails } from "./OrderDraftDetails";
|
||||||
|
import { OrderNormalDetails } from "./OrderNormalDetails";
|
||||||
|
import { OrderUnconfirmedDetails } from "./OrderUnconfirmedDetails";
|
||||||
|
|
||||||
|
interface OrderDetailsProps {
|
||||||
|
id: string;
|
||||||
|
params: OrderUrlQueryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
||||||
|
const navigate = useNavigator();
|
||||||
|
|
||||||
|
const { queue } = useBackgroundTask();
|
||||||
|
const intl = useIntl();
|
||||||
|
const [updateMetadata, updateMetadataOpts] = useMetadataUpdate({});
|
||||||
|
const [
|
||||||
|
updatePrivateMetadata,
|
||||||
|
updatePrivateMetadataOpts
|
||||||
|
] = usePrivateMetadataUpdate({});
|
||||||
|
const notify = useNotifier();
|
||||||
|
|
||||||
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
|
OrderUrlDialog,
|
||||||
|
OrderUrlQueryParams
|
||||||
|
>(navigate, params => orderUrl(id, params), params);
|
||||||
|
|
||||||
|
const handleBack = () => navigate(orderListUrl());
|
||||||
|
|
||||||
|
const [orderConfirm] = useOrderConfirmMutation({
|
||||||
|
onCompleted: ({ orderConfirm: { errors } }) => {
|
||||||
|
const isError = !!errors.length;
|
||||||
|
|
||||||
|
notify({
|
||||||
|
status: isError ? "error" : "success",
|
||||||
|
text: isError
|
||||||
|
? getOrderErrorMessage(errors[0], intl)
|
||||||
|
: "Confirmed Order"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TypedOrderDetailsQuery displayLoader variables={{ id }}>
|
||||||
|
{({ data, loading }) => {
|
||||||
|
const order = data?.order;
|
||||||
|
if (order === null) {
|
||||||
|
return <NotFoundPage onBack={handleBack} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOrderUnconfirmed = order?.status === OrderStatus.UNCONFIRMED;
|
||||||
|
const isOrderDraft = order?.status === OrderStatus.DRAFT;
|
||||||
|
|
||||||
|
const handleSubmit = async (data: MetadataFormData) => {
|
||||||
|
if (order?.status === OrderStatus.UNCONFIRMED) {
|
||||||
|
await orderConfirm({ variables: { id: order?.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = createMetadataUpdateHandler(
|
||||||
|
order,
|
||||||
|
() => Promise.resolve([]),
|
||||||
|
variables => updateMetadata({ variables }),
|
||||||
|
variables => updatePrivateMetadata({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await update(data);
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderDetailsMessages id={id} params={params}>
|
||||||
|
{orderMessages => (
|
||||||
|
<OrderOperations
|
||||||
|
order={id}
|
||||||
|
onNoteAdd={orderMessages.handleNoteAdd}
|
||||||
|
onOrderCancel={orderMessages.handleOrderCancel}
|
||||||
|
onOrderVoid={orderMessages.handleOrderVoid}
|
||||||
|
onPaymentCapture={orderMessages.handlePaymentCapture}
|
||||||
|
onUpdate={orderMessages.handleUpdate}
|
||||||
|
onDraftUpdate={orderMessages.handleDraftUpdate}
|
||||||
|
onShippingMethodUpdate={data => {
|
||||||
|
orderMessages.handleShippingMethodUpdate(data);
|
||||||
|
order.total = data.orderUpdateShipping.order.total;
|
||||||
|
}}
|
||||||
|
onOrderLineDelete={orderMessages.handleOrderLineDelete}
|
||||||
|
onOrderLinesAdd={orderMessages.handleOrderLinesAdd}
|
||||||
|
onOrderLineUpdate={orderMessages.handleOrderLineUpdate}
|
||||||
|
onOrderFulfillmentApprove={
|
||||||
|
orderMessages.handleOrderFulfillmentApprove
|
||||||
|
}
|
||||||
|
onOrderFulfillmentCancel={
|
||||||
|
orderMessages.handleOrderFulfillmentCancel
|
||||||
|
}
|
||||||
|
onOrderFulfillmentUpdate={
|
||||||
|
orderMessages.handleOrderFulfillmentUpdate
|
||||||
|
}
|
||||||
|
onDraftFinalize={orderMessages.handleDraftFinalize}
|
||||||
|
onDraftCancel={orderMessages.handleDraftCancel}
|
||||||
|
onOrderMarkAsPaid={orderMessages.handleOrderMarkAsPaid}
|
||||||
|
onInvoiceRequest={(data: InvoiceRequest) => {
|
||||||
|
if (
|
||||||
|
data.invoiceRequest.invoice.status === JobStatusEnum.SUCCESS
|
||||||
|
) {
|
||||||
|
orderMessages.handleInvoiceGenerateFinished(data);
|
||||||
|
} else {
|
||||||
|
orderMessages.handleInvoiceGeneratePending(data);
|
||||||
|
queue(Task.INVOICE_GENERATE, {
|
||||||
|
generateInvoice: {
|
||||||
|
invoiceId: data.invoiceRequest.invoice.id,
|
||||||
|
orderId: id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onInvoiceSend={orderMessages.handleInvoiceSend}
|
||||||
|
>
|
||||||
|
{({
|
||||||
|
orderAddNote,
|
||||||
|
orderCancel,
|
||||||
|
orderDraftUpdate,
|
||||||
|
orderLinesAdd,
|
||||||
|
orderLineDelete,
|
||||||
|
orderLineUpdate,
|
||||||
|
orderPaymentCapture,
|
||||||
|
orderVoid,
|
||||||
|
orderShippingMethodUpdate,
|
||||||
|
orderUpdate,
|
||||||
|
orderFulfillmentApprove,
|
||||||
|
orderFulfillmentCancel,
|
||||||
|
orderFulfillmentUpdateTracking,
|
||||||
|
orderDraftCancel,
|
||||||
|
orderDraftFinalize,
|
||||||
|
orderPaymentMarkAsPaid,
|
||||||
|
orderInvoiceRequest,
|
||||||
|
orderInvoiceSend
|
||||||
|
}) => (
|
||||||
|
<>
|
||||||
|
{!isOrderDraft && !isOrderUnconfirmed && (
|
||||||
|
<OrderNormalDetails
|
||||||
|
id={id}
|
||||||
|
params={params}
|
||||||
|
data={data}
|
||||||
|
orderAddNote={orderAddNote}
|
||||||
|
orderInvoiceRequest={orderInvoiceRequest}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
orderCancel={orderCancel}
|
||||||
|
orderPaymentMarkAsPaid={orderPaymentMarkAsPaid}
|
||||||
|
orderVoid={orderVoid}
|
||||||
|
orderPaymentCapture={orderPaymentCapture}
|
||||||
|
orderFulfillmentApprove={orderFulfillmentApprove}
|
||||||
|
orderFulfillmentCancel={orderFulfillmentCancel}
|
||||||
|
orderFulfillmentUpdateTracking={
|
||||||
|
orderFulfillmentUpdateTracking
|
||||||
|
}
|
||||||
|
orderInvoiceSend={orderInvoiceSend}
|
||||||
|
updateMetadataOpts={updateMetadataOpts}
|
||||||
|
updatePrivateMetadataOpts={updatePrivateMetadataOpts}
|
||||||
|
openModal={openModal}
|
||||||
|
closeModal={closeModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isOrderDraft && (
|
||||||
|
<OrderDraftDetails
|
||||||
|
id={id}
|
||||||
|
params={params}
|
||||||
|
loading={loading}
|
||||||
|
data={data}
|
||||||
|
orderAddNote={orderAddNote}
|
||||||
|
orderLineUpdate={orderLineUpdate}
|
||||||
|
orderLineDelete={orderLineDelete}
|
||||||
|
orderShippingMethodUpdate={orderShippingMethodUpdate}
|
||||||
|
orderLinesAdd={orderLinesAdd}
|
||||||
|
orderDraftUpdate={orderDraftUpdate}
|
||||||
|
orderDraftCancel={orderDraftCancel}
|
||||||
|
orderDraftFinalize={orderDraftFinalize}
|
||||||
|
openModal={openModal}
|
||||||
|
closeModal={closeModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isOrderUnconfirmed && (
|
||||||
|
<OrderUnconfirmedDetails
|
||||||
|
id={id}
|
||||||
|
params={params}
|
||||||
|
data={data}
|
||||||
|
orderAddNote={orderAddNote}
|
||||||
|
orderLineUpdate={orderLineUpdate}
|
||||||
|
orderLineDelete={orderLineDelete}
|
||||||
|
orderInvoiceRequest={orderInvoiceRequest}
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
orderCancel={orderCancel}
|
||||||
|
orderShippingMethodUpdate={orderShippingMethodUpdate}
|
||||||
|
orderLinesAdd={orderLinesAdd}
|
||||||
|
orderPaymentMarkAsPaid={orderPaymentMarkAsPaid}
|
||||||
|
orderVoid={orderVoid}
|
||||||
|
orderPaymentCapture={orderPaymentCapture}
|
||||||
|
orderFulfillmentApprove={orderFulfillmentApprove}
|
||||||
|
orderFulfillmentCancel={orderFulfillmentCancel}
|
||||||
|
orderFulfillmentUpdateTracking={
|
||||||
|
orderFulfillmentUpdateTracking
|
||||||
|
}
|
||||||
|
orderInvoiceSend={orderInvoiceSend}
|
||||||
|
updateMetadataOpts={updateMetadataOpts}
|
||||||
|
updatePrivateMetadataOpts={updatePrivateMetadataOpts}
|
||||||
|
openModal={openModal}
|
||||||
|
closeModal={closeModal}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<OrderAddressFields
|
||||||
|
isDraft={order?.status === OrderStatus.DRAFT}
|
||||||
|
orderUpdate={orderUpdate}
|
||||||
|
orderDraftUpdate={orderDraftUpdate}
|
||||||
|
data={data}
|
||||||
|
id={id}
|
||||||
|
onClose={closeModal}
|
||||||
|
action={params.action}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</OrderOperations>
|
||||||
|
)}
|
||||||
|
</OrderDetailsMessages>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</TypedOrderDetailsQuery>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OrderDetails;
|
|
@ -22,7 +22,10 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { customerUrl } from "../../../../customers/urls";
|
import { customerUrl } from "../../../../customers/urls";
|
||||||
import { getStringOrPlaceholder } from "../../../../misc";
|
import {
|
||||||
|
extractMutationErrors,
|
||||||
|
getStringOrPlaceholder
|
||||||
|
} from "../../../../misc";
|
||||||
import { productUrl } from "../../../../products/urls";
|
import { productUrl } from "../../../../products/urls";
|
||||||
import OrderDraftCancelDialog from "../../../components/OrderDraftCancelDialog/OrderDraftCancelDialog";
|
import OrderDraftCancelDialog from "../../../components/OrderDraftCancelDialog/OrderDraftCancelDialog";
|
||||||
import OrderDraftPage from "../../../components/OrderDraftPage";
|
import OrderDraftPage from "../../../components/OrderDraftPage";
|
||||||
|
@ -172,10 +175,12 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
<OrderDraftPage
|
<OrderDraftPage
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
input: variables,
|
input: variables,
|
||||||
order: id
|
order: id
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
users={mapEdgesToItems(users?.data?.search)}
|
users={mapEdgesToItems(users?.data?.search)}
|
||||||
hasMore={users?.data?.search?.pageInfo?.hasNextPage || false}
|
hasMore={users?.data?.search?.pageInfo?.hasNextPage || false}
|
||||||
|
@ -245,6 +250,7 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
onFetch={variantSearch}
|
onFetch={variantSearch}
|
||||||
onFetchMore={loadMore}
|
onFetchMore={loadMore}
|
||||||
onSubmit={variants =>
|
onSubmit={variants =>
|
||||||
|
extractMutationErrors(
|
||||||
orderLinesAdd.mutate({
|
orderLinesAdd.mutate({
|
||||||
id,
|
id,
|
||||||
input: variants.map(variant => ({
|
input: variants.map(variant => ({
|
||||||
|
@ -252,6 +258,7 @@ export const OrderDraftDetails: React.FC<OrderDraftDetailsProps> = ({
|
||||||
variantId: variant.id
|
variantId: variant.id
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<OrderCustomerChangeDialog
|
<OrderCustomerChangeDialog
|
||||||
|
|
|
@ -15,7 +15,11 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { customerUrl } from "../../../../customers/urls";
|
import { customerUrl } from "../../../../customers/urls";
|
||||||
import { getMutationState, getStringOrPlaceholder } from "../../../../misc";
|
import {
|
||||||
|
extractMutationErrors,
|
||||||
|
getMutationState,
|
||||||
|
getStringOrPlaceholder
|
||||||
|
} from "../../../../misc";
|
||||||
import { productUrl } from "../../../../products/urls";
|
import { productUrl } from "../../../../products/urls";
|
||||||
import { FulfillmentStatus } from "../../../../types/globalTypes";
|
import { FulfillmentStatus } from "../../../../types/globalTypes";
|
||||||
import OrderCancelDialog from "../../../components/OrderCancelDialog";
|
import OrderCancelDialog from "../../../components/OrderCancelDialog";
|
||||||
|
@ -113,10 +117,12 @@ export const OrderNormalDetails: React.FC<OrderNormalDetailsProps> = ({
|
||||||
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
||||||
}
|
}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
input: variables,
|
input: variables,
|
||||||
order: id
|
order: id
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
order={order}
|
order={order}
|
||||||
|
|
|
@ -18,7 +18,11 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { customerUrl } from "../../../../customers/urls";
|
import { customerUrl } from "../../../../customers/urls";
|
||||||
import { getMutationState, getStringOrPlaceholder } from "../../../../misc";
|
import {
|
||||||
|
extractMutationErrors,
|
||||||
|
getMutationState,
|
||||||
|
getStringOrPlaceholder
|
||||||
|
} from "../../../../misc";
|
||||||
import { productUrl } from "../../../../products/urls";
|
import { productUrl } from "../../../../products/urls";
|
||||||
import { FulfillmentStatus } from "../../../../types/globalTypes";
|
import { FulfillmentStatus } from "../../../../types/globalTypes";
|
||||||
import OrderCancelDialog from "../../../components/OrderCancelDialog";
|
import OrderCancelDialog from "../../../components/OrderCancelDialog";
|
||||||
|
@ -136,10 +140,12 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
|
||||||
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
updateMetadataOpts.loading || updatePrivateMetadataOpts.loading
|
||||||
}
|
}
|
||||||
onNoteAdd={variables =>
|
onNoteAdd={variables =>
|
||||||
|
extractMutationErrors(
|
||||||
orderAddNote.mutate({
|
orderAddNote.mutate({
|
||||||
input: variables,
|
input: variables,
|
||||||
order: id
|
order: id
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
onBack={handleBack}
|
onBack={handleBack}
|
||||||
order={order}
|
order={order}
|
||||||
|
@ -248,12 +254,14 @@ export const OrderUnconfirmedDetails: React.FC<OrderUnconfirmedDetailsProps> = (
|
||||||
shippingMethods={order?.shippingMethods}
|
shippingMethods={order?.shippingMethods}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onSubmit={variables =>
|
onSubmit={variables =>
|
||||||
|
extractMutationErrors(
|
||||||
orderShippingMethodUpdate.mutate({
|
orderShippingMethodUpdate.mutate({
|
||||||
id,
|
id,
|
||||||
input: {
|
input: {
|
||||||
shippingMethod: variables.shippingMethod
|
shippingMethod: variables.shippingMethod
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<OrderProductAddDialog
|
<OrderProductAddDialog
|
||||||
|
|
|
@ -1,289 +1,2 @@
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
export * from "./OrderDetails";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
export { default } from "./OrderDetails";
|
||||||
import { Task } from "@saleor/containers/BackgroundTasks/types";
|
|
||||||
import useBackgroundTask from "@saleor/hooks/useBackgroundTask";
|
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
|
||||||
import { commonMessages } from "@saleor/intl";
|
|
||||||
import { useOrderConfirmMutation } from "@saleor/orders/mutations";
|
|
||||||
import { InvoiceRequest } from "@saleor/orders/types/InvoiceRequest";
|
|
||||||
import getOrderErrorMessage from "@saleor/utils/errors/order";
|
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
|
||||||
import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler";
|
|
||||||
import {
|
|
||||||
useMetadataUpdate,
|
|
||||||
usePrivateMetadataUpdate
|
|
||||||
} from "@saleor/utils/metadata/updateMetadata";
|
|
||||||
import React from "react";
|
|
||||||
import { useIntl } from "react-intl";
|
|
||||||
|
|
||||||
import {
|
|
||||||
InvoiceErrorCode,
|
|
||||||
JobStatusEnum,
|
|
||||||
OrderStatus
|
|
||||||
} from "../../../types/globalTypes";
|
|
||||||
import OrderOperations from "../../containers/OrderOperations";
|
|
||||||
import { TypedOrderDetailsQuery } from "../../queries";
|
|
||||||
import {
|
|
||||||
orderListUrl,
|
|
||||||
orderUrl,
|
|
||||||
OrderUrlDialog,
|
|
||||||
OrderUrlQueryParams
|
|
||||||
} from "../../urls";
|
|
||||||
import OrderAddressFields from "./OrderAddressFields";
|
|
||||||
import { OrderDetailsMessages } from "./OrderDetailsMessages";
|
|
||||||
import { OrderDraftDetails } from "./OrderDraftDetails";
|
|
||||||
import { OrderNormalDetails } from "./OrderNormalDetails";
|
|
||||||
import { OrderUnconfirmedDetails } from "./OrderUnconfirmedDetails";
|
|
||||||
|
|
||||||
interface OrderDetailsProps {
|
|
||||||
id: string;
|
|
||||||
params: OrderUrlQueryParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OrderDetails: React.FC<OrderDetailsProps> = ({ id, params }) => {
|
|
||||||
const navigate = useNavigator();
|
|
||||||
|
|
||||||
const { queue } = useBackgroundTask();
|
|
||||||
const intl = useIntl();
|
|
||||||
const [updateMetadata, updateMetadataOpts] = useMetadataUpdate({});
|
|
||||||
const [
|
|
||||||
updatePrivateMetadata,
|
|
||||||
updatePrivateMetadataOpts
|
|
||||||
] = usePrivateMetadataUpdate({});
|
|
||||||
const notify = useNotifier();
|
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
|
||||||
OrderUrlDialog,
|
|
||||||
OrderUrlQueryParams
|
|
||||||
>(navigate, params => orderUrl(id, params), params);
|
|
||||||
|
|
||||||
const handleBack = () => navigate(orderListUrl());
|
|
||||||
|
|
||||||
const [orderConfirm] = useOrderConfirmMutation({
|
|
||||||
onCompleted: ({ orderConfirm: { errors } }) => {
|
|
||||||
const isError = !!errors.length;
|
|
||||||
|
|
||||||
notify({
|
|
||||||
status: isError ? "error" : "success",
|
|
||||||
text: isError
|
|
||||||
? getOrderErrorMessage(errors[0], intl)
|
|
||||||
: "Confirmed Order"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TypedOrderDetailsQuery displayLoader variables={{ id }}>
|
|
||||||
{({ data, loading }) => {
|
|
||||||
const order = data?.order;
|
|
||||||
if (order === null) {
|
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isOrderUnconfirmed = order?.status === OrderStatus.UNCONFIRMED;
|
|
||||||
const isOrderDraft = order?.status === OrderStatus.DRAFT;
|
|
||||||
|
|
||||||
const handleSubmit = async (data: MetadataFormData) => {
|
|
||||||
if (order?.status === OrderStatus.UNCONFIRMED) {
|
|
||||||
await orderConfirm({ variables: { id: order?.id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = createMetadataUpdateHandler(
|
|
||||||
{
|
|
||||||
id: order.token,
|
|
||||||
metadata: order.metadata,
|
|
||||||
privateMetadata: order.privateMetadata
|
|
||||||
},
|
|
||||||
() => Promise.resolve([]),
|
|
||||||
variables => updateMetadata({ variables }),
|
|
||||||
variables => updatePrivateMetadata({ variables })
|
|
||||||
);
|
|
||||||
const result = await update(data);
|
|
||||||
|
|
||||||
if (result.length === 0) {
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OrderDetailsMessages id={id} params={params}>
|
|
||||||
{orderMessages => (
|
|
||||||
<OrderOperations
|
|
||||||
order={id}
|
|
||||||
onNoteAdd={orderMessages.handleNoteAdd}
|
|
||||||
onOrderCancel={orderMessages.handleOrderCancel}
|
|
||||||
onOrderVoid={orderMessages.handleOrderVoid}
|
|
||||||
onPaymentCapture={orderMessages.handlePaymentCapture}
|
|
||||||
onUpdate={orderMessages.handleUpdate}
|
|
||||||
onDraftUpdate={orderMessages.handleDraftUpdate}
|
|
||||||
onShippingMethodUpdate={data => {
|
|
||||||
orderMessages.handleShippingMethodUpdate(data);
|
|
||||||
order.total = data.orderUpdateShipping.order.total;
|
|
||||||
}}
|
|
||||||
onOrderLineDelete={orderMessages.handleOrderLineDelete}
|
|
||||||
onOrderLinesAdd={orderMessages.handleOrderLinesAdd}
|
|
||||||
onOrderLineUpdate={orderMessages.handleOrderLineUpdate}
|
|
||||||
onOrderFulfillmentApprove={
|
|
||||||
orderMessages.handleOrderFulfillmentApprove
|
|
||||||
}
|
|
||||||
onOrderFulfillmentCancel={
|
|
||||||
orderMessages.handleOrderFulfillmentCancel
|
|
||||||
}
|
|
||||||
onOrderFulfillmentUpdate={
|
|
||||||
orderMessages.handleOrderFulfillmentUpdate
|
|
||||||
}
|
|
||||||
onDraftFinalize={orderMessages.handleDraftFinalize}
|
|
||||||
onDraftCancel={orderMessages.handleDraftCancel}
|
|
||||||
onOrderMarkAsPaid={orderMessages.handleOrderMarkAsPaid}
|
|
||||||
onInvoiceRequest={(data: InvoiceRequest) => {
|
|
||||||
if (
|
|
||||||
data.invoiceRequest.errors.some(
|
|
||||||
err => err.code === InvoiceErrorCode.NO_INVOICE_PLUGIN
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
notify({
|
|
||||||
title: intl.formatMessage({
|
|
||||||
defaultMessage: "Could not generate invoice",
|
|
||||||
description: "snackbar title"
|
|
||||||
}),
|
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "No invoice plugin installed",
|
|
||||||
description: "error message"
|
|
||||||
}),
|
|
||||||
status: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
data.invoiceRequest.invoice.status === JobStatusEnum.SUCCESS
|
|
||||||
) {
|
|
||||||
orderMessages.handleInvoiceGenerateFinished(data);
|
|
||||||
} else {
|
|
||||||
orderMessages.handleInvoiceGeneratePending(data);
|
|
||||||
queue(Task.INVOICE_GENERATE, {
|
|
||||||
generateInvoice: {
|
|
||||||
invoiceId: data.invoiceRequest.invoice.id,
|
|
||||||
orderId: id
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onInvoiceSend={orderMessages.handleInvoiceSend}
|
|
||||||
>
|
|
||||||
{({
|
|
||||||
orderAddNote,
|
|
||||||
orderCancel,
|
|
||||||
orderDraftUpdate,
|
|
||||||
orderLinesAdd,
|
|
||||||
orderLineDelete,
|
|
||||||
orderLineUpdate,
|
|
||||||
orderPaymentCapture,
|
|
||||||
orderVoid,
|
|
||||||
orderShippingMethodUpdate,
|
|
||||||
orderUpdate,
|
|
||||||
orderFulfillmentApprove,
|
|
||||||
orderFulfillmentCancel,
|
|
||||||
orderFulfillmentUpdateTracking,
|
|
||||||
orderDraftCancel,
|
|
||||||
orderDraftFinalize,
|
|
||||||
orderPaymentMarkAsPaid,
|
|
||||||
orderInvoiceRequest,
|
|
||||||
orderInvoiceSend
|
|
||||||
}) => (
|
|
||||||
<>
|
|
||||||
{!isOrderDraft && !isOrderUnconfirmed && (
|
|
||||||
<OrderNormalDetails
|
|
||||||
id={id}
|
|
||||||
params={params}
|
|
||||||
data={data}
|
|
||||||
orderAddNote={orderAddNote}
|
|
||||||
orderInvoiceRequest={orderInvoiceRequest}
|
|
||||||
handleSubmit={handleSubmit}
|
|
||||||
orderCancel={orderCancel}
|
|
||||||
orderPaymentMarkAsPaid={orderPaymentMarkAsPaid}
|
|
||||||
orderVoid={orderVoid}
|
|
||||||
orderPaymentCapture={orderPaymentCapture}
|
|
||||||
orderFulfillmentApprove={orderFulfillmentApprove}
|
|
||||||
orderFulfillmentCancel={orderFulfillmentCancel}
|
|
||||||
orderFulfillmentUpdateTracking={
|
|
||||||
orderFulfillmentUpdateTracking
|
|
||||||
}
|
|
||||||
orderInvoiceSend={orderInvoiceSend}
|
|
||||||
updateMetadataOpts={updateMetadataOpts}
|
|
||||||
updatePrivateMetadataOpts={updatePrivateMetadataOpts}
|
|
||||||
openModal={openModal}
|
|
||||||
closeModal={closeModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOrderDraft && (
|
|
||||||
<OrderDraftDetails
|
|
||||||
id={id}
|
|
||||||
params={params}
|
|
||||||
loading={loading}
|
|
||||||
data={data}
|
|
||||||
orderAddNote={orderAddNote}
|
|
||||||
orderLineUpdate={orderLineUpdate}
|
|
||||||
orderLineDelete={orderLineDelete}
|
|
||||||
orderShippingMethodUpdate={orderShippingMethodUpdate}
|
|
||||||
orderLinesAdd={orderLinesAdd}
|
|
||||||
orderDraftUpdate={orderDraftUpdate}
|
|
||||||
orderDraftCancel={orderDraftCancel}
|
|
||||||
orderDraftFinalize={orderDraftFinalize}
|
|
||||||
openModal={openModal}
|
|
||||||
closeModal={closeModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOrderUnconfirmed && (
|
|
||||||
<OrderUnconfirmedDetails
|
|
||||||
id={id}
|
|
||||||
params={params}
|
|
||||||
data={data}
|
|
||||||
orderAddNote={orderAddNote}
|
|
||||||
orderLineUpdate={orderLineUpdate}
|
|
||||||
orderLineDelete={orderLineDelete}
|
|
||||||
orderInvoiceRequest={orderInvoiceRequest}
|
|
||||||
handleSubmit={handleSubmit}
|
|
||||||
orderCancel={orderCancel}
|
|
||||||
orderShippingMethodUpdate={orderShippingMethodUpdate}
|
|
||||||
orderLinesAdd={orderLinesAdd}
|
|
||||||
orderPaymentMarkAsPaid={orderPaymentMarkAsPaid}
|
|
||||||
orderVoid={orderVoid}
|
|
||||||
orderPaymentCapture={orderPaymentCapture}
|
|
||||||
orderFulfillmentApprove={orderFulfillmentApprove}
|
|
||||||
orderFulfillmentCancel={orderFulfillmentCancel}
|
|
||||||
orderFulfillmentUpdateTracking={
|
|
||||||
orderFulfillmentUpdateTracking
|
|
||||||
}
|
|
||||||
orderInvoiceSend={orderInvoiceSend}
|
|
||||||
updateMetadataOpts={updateMetadataOpts}
|
|
||||||
updatePrivateMetadataOpts={updatePrivateMetadataOpts}
|
|
||||||
openModal={openModal}
|
|
||||||
closeModal={closeModal}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<OrderAddressFields
|
|
||||||
isDraft={order?.status === OrderStatus.DRAFT}
|
|
||||||
orderUpdate={orderUpdate}
|
|
||||||
orderDraftUpdate={orderDraftUpdate}
|
|
||||||
data={data}
|
|
||||||
id={id}
|
|
||||||
onClose={closeModal}
|
|
||||||
action={params.action}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</OrderOperations>
|
|
||||||
)}
|
|
||||||
</OrderDetailsMessages>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedOrderDetailsQuery>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OrderDetails;
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import OrderFulfillPage from "@saleor/orders/components/OrderFulfillPage";
|
import OrderFulfillPage from "@saleor/orders/components/OrderFulfillPage";
|
||||||
import { useOrderFulfill } from "@saleor/orders/mutations";
|
import { useOrderFulfill } from "@saleor/orders/mutations";
|
||||||
import {
|
import {
|
||||||
|
@ -100,6 +101,7 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
||||||
errors={fulfillOrderOpts.data?.orderFulfill.errors}
|
errors={fulfillOrderOpts.data?.orderFulfill.errors}
|
||||||
onBack={() => navigate(orderUrl(orderId))}
|
onBack={() => navigate(orderUrl(orderId))}
|
||||||
onSubmit={formData =>
|
onSubmit={formData =>
|
||||||
|
extractMutationErrors(
|
||||||
fulfillOrder({
|
fulfillOrder({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
|
@ -113,6 +115,7 @@ const OrderFulfill: React.FC<OrderFulfillProps> = ({ orderId }) => {
|
||||||
orderId
|
orderId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
order={data?.order}
|
order={data?.order}
|
||||||
saveButtonBar="default"
|
saveButtonBar="default"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import OrderRefundPage from "@saleor/orders/components/OrderRefundPage";
|
import OrderRefundPage from "@saleor/orders/components/OrderRefundPage";
|
||||||
import {
|
import {
|
||||||
OrderRefundAmountCalculationMode,
|
OrderRefundAmountCalculationMode,
|
||||||
|
@ -101,14 +102,14 @@ const OrderRefund: React.FC<OrderRefundProps> = ({ orderId }) => {
|
||||||
const handleSubmitMiscellaneousRefund = async (
|
const handleSubmitMiscellaneousRefund = async (
|
||||||
formData: OrderRefundSubmitData
|
formData: OrderRefundSubmitData
|
||||||
) => {
|
) => {
|
||||||
const response = await refundOrder({
|
extractMutationErrors(
|
||||||
|
refundOrder({
|
||||||
variables: {
|
variables: {
|
||||||
amount: formData.amount,
|
amount: formData.amount,
|
||||||
id: orderId
|
id: orderId
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return response?.errors || [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitProductsRefund = async (
|
const handleSubmitProductsRefund = async (
|
||||||
|
@ -120,14 +121,14 @@ const OrderRefund: React.FC<OrderRefundProps> = ({ orderId }) => {
|
||||||
? getAutomaticallyCalculatedProductsRefundInput(formData)
|
? getAutomaticallyCalculatedProductsRefundInput(formData)
|
||||||
: getManuallySetProductsRefundInput(formData);
|
: getManuallySetProductsRefundInput(formData);
|
||||||
|
|
||||||
const response = await refundOrderFulfillmentProducts({
|
return extractMutationErrors(
|
||||||
|
refundOrderFulfillmentProducts({
|
||||||
variables: {
|
variables: {
|
||||||
input,
|
input,
|
||||||
order: orderId
|
order: orderId
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return response?.errors || [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (formData: OrderRefundSubmitData) =>
|
const handleSubmit = async (formData: OrderRefundSubmitData) =>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import { extractMutationErrors } from "@saleor/misc";
|
||||||
import OrderReturnPage from "@saleor/orders/components/OrderReturnPage";
|
import OrderReturnPage from "@saleor/orders/components/OrderReturnPage";
|
||||||
import { OrderReturnFormData } from "@saleor/orders/components/OrderReturnPage/form";
|
import { OrderReturnFormData } from "@saleor/orders/components/OrderReturnPage/form";
|
||||||
import { useOrderReturnCreateMutation } from "@saleor/orders/mutations";
|
import { useOrderReturnCreateMutation } from "@saleor/orders/mutations";
|
||||||
import { useOrderQuery } from "@saleor/orders/queries";
|
import { useOrderQuery } from "@saleor/orders/queries";
|
||||||
import { FulfillmentReturnProducts_orderFulfillmentReturnProducts } from "@saleor/orders/types/FulfillmentReturnProducts";
|
|
||||||
import { orderUrl } from "@saleor/orders/urls";
|
import { orderUrl } from "@saleor/orders/urls";
|
||||||
import { OrderErrorCode } from "@saleor/types/globalTypes";
|
import { OrderErrorCode } from "@saleor/types/globalTypes";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
@ -85,20 +85,14 @@ const OrderReturn: React.FC<OrderReturnProps> = ({ orderId }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await returnCreate({
|
return extractMutationErrors(
|
||||||
|
returnCreate({
|
||||||
variables: {
|
variables: {
|
||||||
id: data.order.id,
|
id: data.order.id,
|
||||||
input: new ReturnFormDataParser(data.order, formData).getParsedData()
|
input: new ReturnFormDataParser(data.order, formData).getParsedData()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
orderFulfillmentReturnProducts = {} as FulfillmentReturnProducts_orderFulfillmentReturnProducts
|
|
||||||
} = {}
|
|
||||||
} = result || {};
|
|
||||||
|
|
||||||
return orderFulfillmentReturnProducts.errors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToOrder = (id?: string) => navigate(orderUrl(id || orderId));
|
const navigateToOrder = (id?: string) => navigate(orderUrl(id || orderId));
|
||||||
|
|
|
@ -1,34 +1,53 @@
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getMutationState } from "@saleor/misc";
|
import { extractMutationErrors, getMutationState } from "@saleor/misc";
|
||||||
import OrderSettingsPage from "@saleor/orders/components/OrderSettingsPage";
|
import OrderSettingsPage from "@saleor/orders/components/OrderSettingsPage";
|
||||||
import { OrderSettingsFormData } from "@saleor/orders/components/OrderSettingsPage/form";
|
|
||||||
import { useOrderSettingsUpdateMutation } from "@saleor/orders/mutations";
|
import { useOrderSettingsUpdateMutation } from "@saleor/orders/mutations";
|
||||||
import { useOrderSettingsQuery } from "@saleor/orders/queries";
|
import { useOrderSettingsQuery } from "@saleor/orders/queries";
|
||||||
import { orderListUrl } from "@saleor/orders/urls";
|
import { orderListUrl } from "@saleor/orders/urls";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { OrderSettingsFormData } from "../components/OrderSettingsPage/types";
|
||||||
|
|
||||||
export const OrderSettings: React.FC = () => {
|
export const OrderSettings: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
|
|
||||||
const { data, loading } = useOrderSettingsQuery();
|
const { data, loading } = useOrderSettingsQuery({});
|
||||||
|
|
||||||
const [
|
const [
|
||||||
orderSettingsUpdate,
|
orderSettingsUpdate,
|
||||||
orderSettingsUpdateOpts
|
orderSettingsUpdateOpts
|
||||||
] = useOrderSettingsUpdateMutation({});
|
] = useOrderSettingsUpdateMutation({
|
||||||
|
onCompleted: ({ orderSettingsUpdate: { errors } }) => {
|
||||||
|
if (!errors.length) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBack = () => navigate(orderListUrl());
|
||||||
|
|
||||||
const handleSubmit = async ({
|
const handleSubmit = async ({
|
||||||
automaticallyConfirmAllNewOrders,
|
automaticallyConfirmAllNewOrders,
|
||||||
automaticallyFulfillNonShippableGiftCard,
|
automaticallyFulfillNonShippableGiftCard,
|
||||||
fulfillmentAutoApprove,
|
fulfillmentAutoApprove,
|
||||||
fulfillmentAllowUnpaid
|
fulfillmentAllowUnpaid
|
||||||
}: OrderSettingsFormData) => {
|
}: OrderSettingsFormData) =>
|
||||||
const result = await orderSettingsUpdate({
|
extractMutationErrors(
|
||||||
|
orderSettingsUpdate({
|
||||||
variables: {
|
variables: {
|
||||||
orderSettingsInput: {
|
orderSettingsInput: {
|
||||||
automaticallyFulfillNonShippableGiftCard,
|
automaticallyFulfillNonShippableGiftCard,
|
||||||
|
@ -39,25 +58,8 @@ export const OrderSettings: React.FC = () => {
|
||||||
fulfillmentAllowUnpaid
|
fulfillmentAllowUnpaid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
);
|
||||||
const errors = result.data?.orderSettingsUpdate.errors;
|
|
||||||
if (errors.length) {
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
|
||||||
});
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBack = () => navigate(orderListUrl());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrderSettingsPage
|
<OrderSettingsPage
|
||||||
|
|
|
@ -55,7 +55,7 @@ const PageTypeCreatePage: React.FC<PageTypeCreatePageProps> = props => {
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={formInitialData} onSubmit={onSubmit} confirmLeave>
|
<Form confirmLeave initial={formInitialData} onSubmit={onSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit }) => {
|
||||||
const changeMetadata = makeMetadataChangeHandler(change);
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ const PageTypeDetailsPage: React.FC<PageTypeDetailsPageProps> = props => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={formInitialData} onSubmit={handleSubmit} confirmLeave>
|
<Form confirmLeave initial={formInitialData} onSubmit={handleSubmit}>
|
||||||
{({ change, data, hasChanged, submit }) => {
|
{({ change, data, hasChanged, submit }) => {
|
||||||
const changeMetadata = makeMetadataChangeHandler(change);
|
const changeMetadata = makeMetadataChangeHandler(change);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { getMutationErrors } from "@saleor/misc";
|
||||||
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
import createMetadataCreateHandler from "@saleor/utils/handlers/metadataCreateHandler";
|
||||||
import {
|
import {
|
||||||
useMetadataUpdate,
|
useMetadataUpdate,
|
||||||
|
@ -46,8 +47,12 @@ export const PageTypeCreate: React.FC = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data?.pageTypeCreate.pageType?.id || null;
|
return {
|
||||||
|
id: result.data?.pageTypeCreate.pageType?.id || null,
|
||||||
|
errors: getMutationErrors(result)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataCreateHandler(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
handleCreate,
|
handleCreate,
|
||||||
updateMetadata,
|
updateMetadata,
|
||||||
|
|
|
@ -10,13 +10,19 @@ import {
|
||||||
createFetchReferencesHandler
|
createFetchReferencesHandler
|
||||||
} from "@saleor/attributes/utils/handlers";
|
} from "@saleor/attributes/utils/handlers";
|
||||||
import { AttributeInput } from "@saleor/components/Attributes";
|
import { AttributeInput } from "@saleor/components/Attributes";
|
||||||
|
import { useExitFormDialog } from "@saleor/components/Form/useExitFormDialog";
|
||||||
import { MetadataFormData } from "@saleor/components/Metadata";
|
import { MetadataFormData } from "@saleor/components/Metadata";
|
||||||
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
import { RichTextEditorChange } from "@saleor/components/RichTextEditor";
|
||||||
import useForm, { FormChange, SubmitPromise } from "@saleor/hooks/useForm";
|
import useForm, {
|
||||||
|
CommonUseFormResultWithHandlers,
|
||||||
|
FormChange,
|
||||||
|
SubmitPromise
|
||||||
|
} from "@saleor/hooks/useForm";
|
||||||
import useFormset, {
|
import useFormset, {
|
||||||
FormsetChange,
|
FormsetChange,
|
||||||
FormsetData
|
FormsetData
|
||||||
} from "@saleor/hooks/useFormset";
|
} from "@saleor/hooks/useFormset";
|
||||||
|
import useHandleFormSubmit from "@saleor/hooks/useHandleFormSubmit";
|
||||||
import {
|
import {
|
||||||
PageDetails_page,
|
PageDetails_page,
|
||||||
PageDetails_page_pageType
|
PageDetails_page_pageType
|
||||||
|
@ -32,12 +38,11 @@ import { SearchPageTypes_search_edges_node } from "@saleor/searches/types/Search
|
||||||
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
|
import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts";
|
||||||
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
|
import { FetchMoreProps, ReorderEvent } from "@saleor/types";
|
||||||
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
import getPublicationData from "@saleor/utils/data/getPublicationData";
|
||||||
import handleFormSubmit from "@saleor/utils/handlers/handleFormSubmit";
|
|
||||||
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
import { mapMetadataItemToInput } from "@saleor/utils/maps";
|
||||||
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
import getMetadata from "@saleor/utils/metadata/getMetadata";
|
||||||
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
import useMetadataChangeTrigger from "@saleor/utils/metadata/useMetadataChangeTrigger";
|
||||||
import useRichText from "@saleor/utils/richText/useRichText";
|
import useRichText from "@saleor/utils/richText/useRichText";
|
||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
export interface PageFormData extends MetadataFormData {
|
export interface PageFormData extends MetadataFormData {
|
||||||
isPublished: boolean;
|
isPublished: boolean;
|
||||||
|
@ -71,13 +76,10 @@ export interface PageUpdateHandlers {
|
||||||
fetchReferences: (value: string) => void;
|
fetchReferences: (value: string) => void;
|
||||||
fetchMoreReferences: FetchMoreProps;
|
fetchMoreReferences: FetchMoreProps;
|
||||||
}
|
}
|
||||||
export interface UsePageUpdateFormResult {
|
|
||||||
change: FormChange;
|
export interface UsePageUpdateFormResult
|
||||||
data: PageData;
|
extends CommonUseFormResultWithHandlers<PageData, PageUpdateHandlers> {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
handlers: PageUpdateHandlers;
|
|
||||||
hasChanged: boolean;
|
|
||||||
submit: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UsePageFormOpts {
|
export interface UsePageFormOpts {
|
||||||
|
@ -99,14 +101,23 @@ export interface PageFormProps extends UsePageFormOpts {
|
||||||
onSubmit: (data: PageData) => SubmitPromise;
|
onSubmit: (data: PageData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInitialFormData = (page?: PageDetails_page): PageFormData => ({
|
||||||
|
isPublished: page?.isPublished,
|
||||||
|
metadata: page?.metadata?.map(mapMetadataItemToInput) || [],
|
||||||
|
pageType: null,
|
||||||
|
privateMetadata: page?.privateMetadata?.map(mapMetadataItemToInput) || [],
|
||||||
|
publicationDate: page?.publicationDate || "",
|
||||||
|
seoDescription: page?.seoDescription || "",
|
||||||
|
seoTitle: page?.seoTitle || "",
|
||||||
|
slug: page?.slug || "",
|
||||||
|
title: page?.title || ""
|
||||||
|
});
|
||||||
|
|
||||||
function usePageForm(
|
function usePageForm(
|
||||||
page: PageDetails_page,
|
page: PageDetails_page,
|
||||||
onSubmit: (data: PageData) => SubmitPromise,
|
onSubmit: (data: PageData) => SubmitPromise,
|
||||||
opts: UsePageFormOpts
|
opts: UsePageFormOpts
|
||||||
): UsePageUpdateFormResult {
|
): UsePageUpdateFormResult {
|
||||||
const [changed, setChanged] = React.useState(false);
|
|
||||||
const triggerChange = () => setChanged(true);
|
|
||||||
|
|
||||||
const pageExists = page !== null;
|
const pageExists = page !== null;
|
||||||
|
|
||||||
const attributes = useFormset(
|
const attributes = useFormset(
|
||||||
|
@ -118,19 +129,21 @@ function usePageForm(
|
||||||
);
|
);
|
||||||
const attributesWithNewFileValue = useFormset<null, File>([]);
|
const attributesWithNewFileValue = useFormset<null, File>([]);
|
||||||
|
|
||||||
const form = useForm<PageFormData>({
|
const {
|
||||||
isPublished: page?.isPublished,
|
handleChange,
|
||||||
metadata: pageExists ? page?.metadata?.map(mapMetadataItemToInput) : [],
|
triggerChange,
|
||||||
pageType: null,
|
setChanged,
|
||||||
privateMetadata: pageExists
|
hasChanged,
|
||||||
? page?.privateMetadata?.map(mapMetadataItemToInput)
|
data: formData,
|
||||||
: [],
|
formId
|
||||||
publicationDate: page?.publicationDate || "",
|
} = useForm(getInitialFormData(page), undefined, {
|
||||||
seoDescription: page?.seoDescription || "",
|
confirmLeave: true
|
||||||
seoTitle: page?.seoTitle || "",
|
|
||||||
slug: page?.slug || "",
|
|
||||||
title: page?.title || ""
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { setExitDialogSubmitRef } = useExitFormDialog({
|
||||||
|
formId
|
||||||
|
});
|
||||||
|
|
||||||
const [content, changeContent] = useRichText({
|
const [content, changeContent] = useRichText({
|
||||||
initial: pageExists ? page?.content : null,
|
initial: pageExists ? page?.content : null,
|
||||||
triggerChange
|
triggerChange
|
||||||
|
@ -142,10 +155,6 @@ function usePageForm(
|
||||||
makeChangeHandler: makeMetadataChangeHandler
|
makeChangeHandler: makeMetadataChangeHandler
|
||||||
} = useMetadataChangeTrigger();
|
} = useMetadataChangeTrigger();
|
||||||
|
|
||||||
const handleChange: FormChange = (event, cb) => {
|
|
||||||
form.change(event, cb);
|
|
||||||
triggerChange();
|
|
||||||
};
|
|
||||||
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
const changeMetadata = makeMetadataChangeHandler(handleChange);
|
||||||
const handlePageTypeSelect = createPageTypeSelectHandler(
|
const handlePageTypeSelect = createPageTypeSelectHandler(
|
||||||
opts.onSelectPageType,
|
opts.onSelectPageType,
|
||||||
|
@ -191,7 +200,7 @@ function usePageForm(
|
||||||
|
|
||||||
// Need to make it function to always have content.current up to date
|
// Need to make it function to always have content.current up to date
|
||||||
const getData = (): PageData => ({
|
const getData = (): PageData => ({
|
||||||
...form.data,
|
...formData,
|
||||||
attributes: getAttributesDisplayData(
|
attributes: getAttributesDisplayData(
|
||||||
attributes.data,
|
attributes.data,
|
||||||
attributesWithNewFileValue.data,
|
attributesWithNewFileValue.data,
|
||||||
|
@ -204,8 +213,8 @@ function usePageForm(
|
||||||
|
|
||||||
const getSubmitData = (): PageSubmitData => ({
|
const getSubmitData = (): PageSubmitData => ({
|
||||||
...getData(),
|
...getData(),
|
||||||
...getMetadata(form.data, isMetadataModified, isPrivateMetadataModified),
|
...getMetadata(formData, isMetadataModified, isPrivateMetadataModified),
|
||||||
...getPublicationData(form.data),
|
...getPublicationData(formData),
|
||||||
attributesWithNewFileValue: attributesWithNewFileValue.data
|
attributesWithNewFileValue: attributesWithNewFileValue.data
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -219,10 +228,15 @@ function usePageForm(
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = () =>
|
const handleFormSubmit = useHandleFormSubmit({
|
||||||
pageExists
|
formId,
|
||||||
? handleFormSubmit(getSubmitData(), handleSubmit, setChanged)
|
onSubmit: handleSubmit,
|
||||||
: onSubmit(getSubmitData());
|
setChanged
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = () => handleFormSubmit(getSubmitData());
|
||||||
|
|
||||||
|
useEffect(() => setExitDialogSubmitRef(submit), [submit]);
|
||||||
|
|
||||||
const valid = pageExists || !!opts.selectedPageType;
|
const valid = pageExists || !!opts.selectedPageType;
|
||||||
|
|
||||||
|
@ -242,7 +256,7 @@ function usePageForm(
|
||||||
selectAttributeReference: handleAttributeReferenceChange,
|
selectAttributeReference: handleAttributeReferenceChange,
|
||||||
selectPageType: handlePageTypeSelect
|
selectPageType: handlePageTypeSelect
|
||||||
},
|
},
|
||||||
hasChanged: changed,
|
hasChanged,
|
||||||
submit
|
submit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { useFileUploadMutation } from "@saleor/files/mutations";
|
import { useFileUploadMutation } from "@saleor/files/mutations";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
|
import { getMutationErrors } from "@saleor/misc";
|
||||||
import usePageSearch from "@saleor/searches/usePageSearch";
|
import usePageSearch from "@saleor/searches/usePageSearch";
|
||||||
import usePageTypeSearch from "@saleor/searches/usePageTypeSearch";
|
import usePageTypeSearch from "@saleor/searches/usePageTypeSearch";
|
||||||
import useProductSearch from "@saleor/searches/useProductSearch";
|
import useProductSearch from "@saleor/searches/useProductSearch";
|
||||||
|
@ -170,8 +171,12 @@ export const PageCreate: React.FC<PageCreateProps> = ({ params }) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data.pageCreate.page?.id || null;
|
return {
|
||||||
|
id: result.data.pageCreate.page?.id || null,
|
||||||
|
errors: getMutationErrors(result)
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = createMetadataCreateHandler(
|
const handleSubmit = createMetadataCreateHandler(
|
||||||
handleCreate,
|
handleCreate,
|
||||||
updateMetadata,
|
updateMetadata,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Form from "@saleor/components/Form";
|
||||||
import Grid from "@saleor/components/Grid";
|
import Grid from "@saleor/components/Grid";
|
||||||
import Savebar from "@saleor/components/Savebar";
|
import Savebar from "@saleor/components/Savebar";
|
||||||
import { PermissionGroupErrorFragment } from "@saleor/fragments/types/PermissionGroupErrorFragment";
|
import { PermissionGroupErrorFragment } from "@saleor/fragments/types/PermissionGroupErrorFragment";
|
||||||
|
import { SubmitPromise } from "@saleor/hooks/useForm";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
import { ConfirmButtonTransitionState } from "@saleor/macaw-ui";
|
||||||
import { Backlink } from "@saleor/macaw-ui";
|
import { Backlink } from "@saleor/macaw-ui";
|
||||||
|
@ -16,14 +17,14 @@ import { useIntl } from "react-intl";
|
||||||
import { PermissionData } from "../PermissionGroupDetailsPage";
|
import { PermissionData } from "../PermissionGroupDetailsPage";
|
||||||
import PermissionGroupInfo from "../PermissionGroupInfo";
|
import PermissionGroupInfo from "../PermissionGroupInfo";
|
||||||
|
|
||||||
export interface PermissionGroupCreatePageFormData {
|
export interface PermissionGroupCreateFormData {
|
||||||
name: string;
|
name: string;
|
||||||
hasFullAccess: boolean;
|
hasFullAccess: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
permissions: PermissionEnum[];
|
permissions: PermissionEnum[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialForm: PermissionGroupCreatePageFormData = {
|
const initialForm: PermissionGroupCreateFormData = {
|
||||||
hasFullAccess: false,
|
hasFullAccess: false,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -36,7 +37,7 @@ export interface PermissionGroupCreatePageProps {
|
||||||
permissions: PermissionData[];
|
permissions: PermissionData[];
|
||||||
saveButtonBarState: ConfirmButtonTransitionState;
|
saveButtonBarState: ConfirmButtonTransitionState;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit(data: PermissionGroupCreatePageFormData);
|
onSubmit: (data: PermissionGroupCreateFormData) => SubmitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
||||||
|
@ -56,7 +57,7 @@ const PermissionGroupCreatePage: React.FC<PermissionGroupCreatePageProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
|
<Form confirmLeave initial={initialForm} onSubmit={onSubmit}>
|
||||||
{({ data, change, submit, hasChanged }) => (
|
{({ data, change, submit, hasChanged }) => (
|
||||||
<Container>
|
<Container>
|
||||||
<Backlink onClick={onBack}>
|
<Backlink onClick={onBack}>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue