From 46c0453e9edf9febe4b1d529803aa95d7f2e2545 Mon Sep 17 00:00:00 2001 From: Wojciech Date: Wed, 31 Jul 2024 12:46:12 +0200 Subject: [PATCH] transaction capture modal improvements --- .changeset/nine-steaks-build.md | 5 ++ .../ButtonWithLoader.test.tsx | 58 ++++++++++++++++++ .../ButtonWithLoader/ButtonWithLoader.tsx | 56 +++++++++++++++++ .../OrderTransactionActionDialog.tsx | 60 ++++++++++--------- .../OrderDetails/OrderNormalDetails/index.tsx | 10 ++-- .../OrderUnconfirmedDetails/index.tsx | 10 ++-- 6 files changed, 163 insertions(+), 36 deletions(-) create mode 100644 .changeset/nine-steaks-build.md create mode 100644 src/components/ButtonWithLoader/ButtonWithLoader.test.tsx create mode 100644 src/components/ButtonWithLoader/ButtonWithLoader.tsx diff --git a/.changeset/nine-steaks-build.md b/.changeset/nine-steaks-build.md new file mode 100644 index 00000000000..fee48fafe7f --- /dev/null +++ b/.changeset/nine-steaks-build.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Transaction capture modal no longer shows "Error" text when API error occurs. This means that the modal closes when mutation finishes so that result is visible. diff --git a/src/components/ButtonWithLoader/ButtonWithLoader.test.tsx b/src/components/ButtonWithLoader/ButtonWithLoader.test.tsx new file mode 100644 index 00000000000..5c61560d117 --- /dev/null +++ b/src/components/ButtonWithLoader/ButtonWithLoader.test.tsx @@ -0,0 +1,58 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; + +import { ButtonWithLoader } from "./ButtonWithLoader"; + +jest.mock("react-intl", () => ({ + useIntl: jest.fn(() => ({ + formatMessage: jest.fn(x => x.defaultMessage), + })), + defineMessages: jest.fn(x => x), + FormattedMessage: ({ defaultMessage }: { defaultMessage: string }) => <>{defaultMessage}, +})); + +describe("ConfirmButton", () => { + it("should render a button with confirm label", () => { + // Arrange & Act + render(Confirm); + // Assert + expect(screen.getByRole("button")).toBeInTheDocument(); + expect(screen.getByRole("button")).toHaveTextContent("Confirm"); + }); + + it("should render a button with loading spinner", () => { + // Arrange & Act + render(); + // Assert + expect(screen.getByRole("button")).toBeInTheDocument(); + expect(screen.getByTestId("button-progress")).toBeInTheDocument(); + }); + + it("should call onClick when clicked", () => { + // Arrange + const onClick = jest.fn(); + + // Act + render(); + fireEvent.click(screen.getByRole("button")); + + // Assert + expect(onClick).toHaveBeenCalled(); + }); + + it("should render original label after loading state", async () => { + // Arrange & Act + const { rerender } = render( + Confirm, + ); + + // Assert + expect(screen.getByTestId("button-progress")).toBeInTheDocument(); + + // Act + rerender(Confirm); + + // Assert + expect(screen.queryByTestId("button-progress")).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/ButtonWithLoader/ButtonWithLoader.tsx b/src/components/ButtonWithLoader/ButtonWithLoader.tsx new file mode 100644 index 00000000000..5d217bce58a --- /dev/null +++ b/src/components/ButtonWithLoader/ButtonWithLoader.tsx @@ -0,0 +1,56 @@ +import { buttonMessages } from "@dashboard/intl"; +import { Box, Button, ButtonProps, Spinner, sprinkles } from "@saleor/macaw-ui-next"; +import React from "react"; +import { useIntl } from "react-intl"; + +import { ConfirmButtonTransitionState } from "../ConfirmButton"; + +interface ButtonWithLoaderProps extends ButtonProps { + transitionState: ConfirmButtonTransitionState; +} + +export const ButtonWithLoader = ({ + transitionState, + onClick, + disabled, + children, + ...props +}: ButtonWithLoaderProps) => { + const intl = useIntl(); + const isLoading = transitionState === "loading"; + + const renderSpinner = () => { + if (isLoading) { + return ( + + + + ); + } + + return null; + }; + + const getByLabelText = () => { + return children || intl.formatMessage(buttonMessages.save); + }; + + return ( + + ); +}; diff --git a/src/orders/components/OrderTransactionActionDialog/OrderTransactionActionDialog.tsx b/src/orders/components/OrderTransactionActionDialog/OrderTransactionActionDialog.tsx index aa0ac7ef3ac..2961b07e860 100644 --- a/src/orders/components/OrderTransactionActionDialog/OrderTransactionActionDialog.tsx +++ b/src/orders/components/OrderTransactionActionDialog/OrderTransactionActionDialog.tsx @@ -1,15 +1,17 @@ -import { ConfirmButton, ConfirmButtonTransitionState } from "@dashboard/components/ConfirmButton"; +import { ButtonWithLoader } from "@dashboard/components/ButtonWithLoader/ButtonWithLoader"; +import { ConfirmButtonTransitionState } from "@dashboard/components/ConfirmButton"; +import { DashboardModal } from "@dashboard/components/Modal"; import { TransactionActionEnum } from "@dashboard/graphql"; import { buttonMessages } from "@dashboard/intl"; -import { Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from "@material-ui/core"; -import { Button } from "@saleor/macaw-ui"; +import { Button, Text } from "@saleor/macaw-ui-next"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { mapActionToMessage } from "../OrderTransaction/utils"; import { messages } from "./messages"; -export interface OrderTransactionActionDialogProps extends DialogProps { +export interface OrderTransactionActionDialogProps { + open: boolean; confirmButtonState: ConfirmButtonTransitionState; onClose: () => void; onSubmit: () => void; @@ -28,29 +30,31 @@ export const OrderTransactionActionDialog: React.FC - - - - - - - - - - - + + + + + + + + + + + + + + + {actionIntl} + + + + ); }; diff --git a/src/orders/views/OrderDetails/OrderNormalDetails/index.tsx b/src/orders/views/OrderDetails/OrderNormalDetails/index.tsx index 94dae9e0fd4..a123f55da96 100644 --- a/src/orders/views/OrderDetails/OrderNormalDetails/index.tsx +++ b/src/orders/views/OrderDetails/OrderNormalDetails/index.tsx @@ -290,10 +290,12 @@ export const OrderNormalDetails: React.FC = ({ open={params.action === "transaction-action"} action={params.type} onSubmit={() => - orderTransactionAction.mutate({ - action: params.type, - transactionId: params.id, - }) + orderTransactionAction + .mutate({ + action: params.type, + transactionId: params.id, + }) + .finally(() => closeModal()) } /> = ( open={params.action === "transaction-action"} action={params.type} onSubmit={() => - orderTransactionAction.mutate({ - action: params.type, - transactionId: params.id, - }) + orderTransactionAction + .mutate({ + action: params.type, + transactionId: params.id, + }) + .finally(() => closeModal()) } />