diff --git a/apps/web/src/components/SendFlow/Delegation/ChangeDelegateNoticeModal.tsx b/apps/web/src/components/SendFlow/Delegation/ChangeDelegateNoticeModal.tsx deleted file mode 100644 index 4b5e89fce7..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/ChangeDelegateNoticeModal.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - Button, - Center, - Heading, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - Text, -} from "@chakra-ui/react"; -import { useDynamicModalContext } from "@umami/components"; -import { type Account } from "@umami/core"; -import { type TzktAlias } from "@umami/tzkt"; - -import { FormPage } from "./FormPage"; -import { StubIcon as WarningIcon } from "../../../assets/icons"; -import { useColor } from "../../../styles/useColor"; - -export const ChangeDelegateNoticeModal = ({ - account, - delegate, -}: { - account: Account; - delegate: TzktAlias; -}) => { - const { openWith } = useDynamicModalContext(); - const color = useColor(); - - return ( - - - -
- - Important notice -
-
- -
- - Changing the baker will automatically unstake all the existing staked balance. This - balance will be finalizable after 4 cycles. - - - -
-
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Delegation/FormPage.test.tsx b/apps/web/src/components/SendFlow/Delegation/FormPage.test.tsx deleted file mode 100644 index 73b993f713..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/FormPage.test.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { estimate, makeAccountOperations, mockImplicitAccount } from "@umami/core"; -import { - type UmamiStore, - accountsActions, - addTestAccount, - assetsActions, - makeStore, - mockToast, -} from "@umami/state"; -import { executeParams } from "@umami/test-utils"; -import { CustomError } from "@umami/utils"; - -import { FormPage, type FormValues } from "./FormPage"; -import { SignPage } from "./SignPage"; -import { - act, - dynamicModalContextMock, - renderInModal, - screen, - userEvent, - waitFor, -} from "../../../testUtils"; -import { type FormPageProps } from "../utils"; - -const fixture = (props: FormPageProps) => ; - -jest.mock("@umami/core", () => ({ - ...jest.requireActual("@umami/core"), - estimate: jest.fn(), -})); - -let store: UmamiStore; - -beforeEach(() => { - store = makeStore(); - addTestAccount(store, mockImplicitAccount(0)); - store.dispatch(accountsActions.setCurrent(mockImplicitAccount(0).address.pkh)); -}); - -describe("
", () => { - describe("default values", () => { - it("renders a form with default form values", async () => { - await renderInModal( - fixture({ - form: { - sender: mockImplicitAccount(0).address.pkh, - baker: mockImplicitAccount(1).address.pkh, - }, - }), - store - ); - - await waitFor(() => { - expect(screen.getByTestId("real-address-input-sender")).toHaveValue( - mockImplicitAccount(0).address.pkh - ); - }); - }); - - it("displays delegate for address who is not delegating", async () => { - const sender = mockImplicitAccount(0); - await renderInModal( - fixture({ - sender, - }), - store - ); - - expect(screen.getByText("Delegate")).toBeInTheDocument(); - }); - - it("displays change baker for address who is delegating", async () => { - const sender = mockImplicitAccount(0); - const baker = mockImplicitAccount(1); - store.dispatch( - assetsActions.updateBakers([ - { address: baker.address.pkh, name: "baker1", stakingBalance: 1 }, - ]) - ); - - await renderInModal( - fixture({ - sender, - form: { - sender: sender.address.pkh, - baker: baker.address.pkh, - }, - }), - store - ); - - await waitFor(() => { - expect(screen.getByText("Change baker")).toBeInTheDocument(); - }); - }); - }); - - it("shows a toast if estimation fails", async () => { - const user = userEvent.setup(); - await renderInModal( - fixture({ - sender: mockImplicitAccount(0), - form: { - sender: mockImplicitAccount(0).address.pkh, - baker: mockImplicitAccount(1).address.pkh, - }, - }), - store - ); - const submitButton = screen.getByText("Preview"); - await waitFor(() => { - expect(submitButton).toBeEnabled(); - }); - - const estimateMock = jest.mocked(estimate); - estimateMock.mockRejectedValue(new CustomError("Some error occurred")); - - await act(() => user.click(submitButton)); - - expect(estimateMock).toHaveBeenCalledTimes(1); - expect(mockToast).toHaveBeenCalledWith({ - description: "Some error occurred", - status: "error", - isClosable: true, - }); - }); - - it("opens a sign page if estimation succeeds", async () => { - const user = userEvent.setup(); - const sender = mockImplicitAccount(0); - await renderInModal( - fixture({ - sender: sender, - form: { - sender: sender.address.pkh, - baker: mockImplicitAccount(1).address.pkh, - }, - }), - store - ); - const submitButton = screen.getByText("Preview"); - await waitFor(() => { - expect(submitButton).toBeEnabled(); - }); - const operations = { - ...makeAccountOperations(sender, sender, [ - { - type: "delegation", - sender: sender.address, - recipient: mockImplicitAccount(1).address, - }, - ]), - estimates: [executeParams()], - }; - - jest.mocked(estimate).mockResolvedValueOnce(operations); - - await act(() => user.click(submitButton)); - - expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - - ); - expect(mockToast).not.toHaveBeenCalled(); - }); -}); diff --git a/apps/web/src/components/SendFlow/Delegation/FormPage.tsx b/apps/web/src/components/SendFlow/Delegation/FormPage.tsx deleted file mode 100644 index 1ab5c8dd01..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/FormPage.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { - FormControl, - FormErrorMessage, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, - Text, -} from "@chakra-ui/react"; -import hj from "@hotjar/browser"; -import { type Delegation } from "@umami/core"; -import { type RawPkh, parseImplicitPkh, parsePkh } from "@umami/tezos"; -import { FormProvider, useForm } from "react-hook-form"; - -import { SignPage } from "./SignPage"; -import { BakersAutocomplete, OwnedAccountsAutocomplete } from "../../AddressAutocomplete"; -import { - useAddToBatchFormAction, - useHandleOnSubmitFormActions, - useOpenSignPageFormAction, -} from "../onSubmitFormActionHooks"; -import { type FormPageProps, FormSubmitButton, formDefaultValues } from "../utils"; - -export type FormValues = { - sender: RawPkh; - baker: RawPkh; -}; - -export const FormPage = (props: FormPageProps) => { - const baker = props.form?.baker; - - hj.stateChange("send_flow/delegation_form_page"); - - const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: undefined, - FormPage, - defaultFormPageProps: props, - toOperation, - }); - - const addToBatch = useAddToBatchFormAction(toOperation); - - const { - onFormSubmitActionHandlers: [onSingleSubmit], - isLoading, - } = useHandleOnSubmitFormActions([openSignPage, addToBatch]); - - const form = useForm({ - mode: "onBlur", - defaultValues: formDefaultValues(props), - }); - - const { - formState: { errors }, - handleSubmit, - } = form; - - return ( - - - - - - {baker ? "Change baker" : "Delegate"} - - - - - - - - {errors.sender && ( - - {errors.sender.message} - - )} - - - - - {errors.baker && {errors.baker.message}} - - - - - - - - - ); -}; - -const toOperation = (formValues: FormValues): Delegation => ({ - type: "delegation", - sender: parsePkh(formValues.sender), - recipient: parseImplicitPkh(formValues.baker), -}); diff --git a/apps/web/src/components/SendFlow/Delegation/NewDelegateNoticeModal.tsx b/apps/web/src/components/SendFlow/Delegation/NewDelegateNoticeModal.tsx deleted file mode 100644 index 54986aa0a4..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/NewDelegateNoticeModal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { - Button, - Center, - Heading, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - Text, -} from "@chakra-ui/react"; -import { useDynamicModalContext } from "@umami/components"; -import { type Account } from "@umami/core"; - -import { FormPage } from "./FormPage"; -import { StubIcon as NoticeIcon } from "../../../assets/icons"; -import { useColor } from "../../../styles/useColor"; -import { NoticeSteps } from "../NoticeSteps"; - -export const NewDelegateNoticeModal = ({ account }: { account: Account }) => { - const { openWith } = useDynamicModalContext(); - const color = useColor(); - - return ( - - - -
- - Delegation -
-
- -
- - Earn risk-free rewards by delegating to a Tezos baker. Delegated funds remain in your - account, and you can always spend them at will. - - - - - -
-
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Delegation/NoticeModal.tsx b/apps/web/src/components/SendFlow/Delegation/NoticeModal.tsx deleted file mode 100644 index ba2d1a6fa3..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/NoticeModal.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { type Account } from "@umami/core"; -import { useGetAccountDelegate } from "@umami/state"; - -import { ChangeDelegateNoticeModal } from "./ChangeDelegateNoticeModal"; -import { NewDelegateNoticeModal } from "./NewDelegateNoticeModal"; - -export const NoticeModal = ({ account }: { account: Account }) => { - const delegate = useGetAccountDelegate()(account.address.pkh); - - if (delegate) { - return ; - } - return ; -}; diff --git a/apps/web/src/components/SendFlow/Delegation/SignPage.test.tsx b/apps/web/src/components/SendFlow/Delegation/SignPage.test.tsx deleted file mode 100644 index 6868583e75..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/SignPage.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Modal } from "@chakra-ui/react"; -import { makeAccountOperations, mockImplicitAccount, mockMnemonicAccount } from "@umami/core"; -import { type UmamiStore, addTestAccount, assetsActions, makeStore } from "@umami/state"; -import { executeParams } from "@umami/test-utils"; -import { TEZ } from "@umami/tezos"; - -import { SignPage } from "./SignPage"; -import { render, screen, waitFor } from "../../../testUtils"; -import { type SignPageProps } from "../utils"; - -const fixture = (props: SignPageProps) => ( - {}}> - - -); - -let store: UmamiStore; - -beforeEach(() => { - store = makeStore(); - addTestAccount(store, mockMnemonicAccount(0)); -}); - -describe("", () => { - const sender = mockImplicitAccount(0); - const operations = { - ...makeAccountOperations(sender, mockImplicitAccount(0), [ - { - type: "delegation", - sender: sender.address, - recipient: mockImplicitAccount(1).address, - }, - ]), - estimates: [executeParams({ fee: 1234567 })], - }; - - it("displays the fee in tez", async () => { - const props: SignPageProps = { - operations, - mode: "single", - data: undefined, - }; - render(fixture(props), { store }); - - await waitFor(() => expect(screen.getByTestId("fee")).toHaveTextContent(`1.234567 ${TEZ}`)); - }); - - it("displays address tile for baker", async () => { - const baker = mockImplicitAccount(1); - - store.dispatch( - assetsActions.updateBakers([ - { address: baker.address.pkh, name: "baker1", stakingBalance: 1 }, - ]) - ); - - const props: SignPageProps = { - operations, - mode: "single", - data: undefined, - }; - render(fixture(props), { store }); - - await waitFor(() => - expect(screen.getAllByTestId("address-tile")[1]).toHaveTextContent("baker1") - ); - }); -}); diff --git a/apps/web/src/components/SendFlow/Delegation/SignPage.tsx b/apps/web/src/components/SendFlow/Delegation/SignPage.tsx deleted file mode 100644 index d8b1432135..0000000000 --- a/apps/web/src/components/SendFlow/Delegation/SignPage.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Delegation } from "@umami/core"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -export const SignPage = (props: SignPageProps) => { - const { operations: initialOperations } = props; - const { fee, operations, estimationFailed, isLoading, form, signer, onSign } = - useSignPageHelpers(initialOperations); - const baker = (operations.operations[0] as Delegation).recipient; - return ( - - -
- - - From - - - - - - - - - To - - - - - - - - -
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/FinalizeUnstake/SignPage.tsx b/apps/web/src/components/SendFlow/FinalizeUnstake/SignPage.tsx deleted file mode 100644 index 5df771a4fa..0000000000 --- a/apps/web/src/components/SendFlow/FinalizeUnstake/SignPage.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - Center, - Flex, - FormLabel, - Heading, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, -} from "@chakra-ui/react"; -import { type BigNumber } from "bignumber.js"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { TezTile } from "../../AssetTiles/TezTile"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -// TODO: test -export const SignPage = (props: SignPageProps<{ finalizableAmount: BigNumber }>) => { - const { - operations, - data: { finalizableAmount }, - } = props; - const { isLoading, form, signer, onSign, fee } = useSignPageHelpers(operations); - - return ( - - -
- -
- Finalize -
- -
- - From - - - - - - - Withdraw - - - - - - - -
-
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Token/SignPage.test.tsx b/apps/web/src/components/SendFlow/LocalSignPage.test.tsx similarity index 57% rename from apps/web/src/components/SendFlow/Token/SignPage.test.tsx rename to apps/web/src/components/SendFlow/LocalSignPage.test.tsx index cf7ff64b66..305dc1ba4b 100644 --- a/apps/web/src/components/SendFlow/Token/SignPage.test.tsx +++ b/apps/web/src/components/SendFlow/LocalSignPage.test.tsx @@ -1,7 +1,5 @@ import { Modal } from "@chakra-ui/react"; import { - type FA12TokenBalance, - type FA2TokenBalance, makeAccountOperations, mockFA2Token, mockImplicitAccount, @@ -9,20 +7,20 @@ import { } from "@umami/core"; import { type UmamiStore, addTestAccount, makeStore } from "@umami/state"; import { executeParams } from "@umami/test-utils"; -import { TEZ, parseContractPkh } from "@umami/tezos"; +import { TEZ, mockImplicitAddress, parseContractPkh } from "@umami/tezos"; -import { SignPage } from "./SignPage"; -import { render, screen, waitFor } from "../../../testUtils"; -import { type SignPageProps } from "../utils"; +import { LocalSignPage } from "./LocalSignPage"; +import { type LocalSignPageProps } from "./utils"; +import { render, screen, waitFor } from "../../testUtils"; jest.mock("@chakra-ui/react", () => ({ ...jest.requireActual("@chakra-ui/react"), useBreakpointValue: jest.fn(), })); -const fixture = (props: SignPageProps<{ token: FA12TokenBalance | FA2TokenBalance }>) => ( +const fixture = (props: LocalSignPageProps) => ( {}}> - + ); @@ -35,7 +33,33 @@ beforeEach(() => { const mockAccount = mockMnemonicAccount(0); const mockFAToken = mockFA2Token(0, mockAccount); -describe("", () => { + +describe("", () => { + describe("fee", () => { + it("displays the fee in tez", async () => { + const store = makeStore(); + addTestAccount(store, mockMnemonicAccount(0)); + const props: LocalSignPageProps = { + operations: { + ...makeAccountOperations(mockImplicitAccount(0), mockImplicitAccount(0), [ + { + type: "tez", + amount: "1000000", + recipient: mockImplicitAddress(1), + }, + ]), + estimates: [executeParams({ fee: 1234567 })], + }, + operationType: "tez", + }; + render(fixture(props), { store }); + + await waitFor(() => expect(screen.getByTestId("fee")).toHaveTextContent(`1.234567 ${TEZ}`)); + }); + }); +}); + +describe("", () => { const sender = mockImplicitAccount(0); const operations = { ...makeAccountOperations(sender, mockImplicitAccount(0), [ @@ -56,12 +80,10 @@ describe("", () => { }; describe("fee", () => { it("displays the fee in tez", async () => { - const props: SignPageProps<{ - token: FA12TokenBalance | FA2TokenBalance; - }> = { + const props: LocalSignPageProps = { operations, - mode: "single", - data: { token: mockFAToken }, + operationType: "token", + token: mockFAToken, }; render(fixture(props), { store }); @@ -71,12 +93,10 @@ describe("", () => { describe("token", () => { it("displays the correct symbol", async () => { - const props: SignPageProps<{ - token: FA12TokenBalance | FA2TokenBalance; - }> = { + const props: LocalSignPageProps = { operations, - mode: "single", - data: { token: mockFAToken }, + operationType: "token", + token: mockFAToken, }; render(fixture(props), { store }); @@ -88,12 +108,10 @@ describe("", () => { }); it("displays the correct amount", async () => { - const props: SignPageProps<{ - token: FA12TokenBalance | FA2TokenBalance; - }> = { + const props: LocalSignPageProps = { operations, - mode: "single", - data: { token: mockFA2Token(0, mockAccount, 1, 0) }, + operationType: "token", + token: mockFA2Token(0, mockAccount, 1, 0), }; render(fixture(props), { store }); diff --git a/apps/web/src/components/SendFlow/LocalSignPage.tsx b/apps/web/src/components/SendFlow/LocalSignPage.tsx new file mode 100644 index 0000000000..1d1e8ce41d --- /dev/null +++ b/apps/web/src/components/SendFlow/LocalSignPage.tsx @@ -0,0 +1,138 @@ +import { + Flex, + FormControl, + FormLabel, + ModalBody, + ModalContent, + ModalFooter, + VStack, + useBreakpointValue, +} from "@chakra-ui/react"; +import { + type FA12TokenBalance, + type FA2TokenBalance, + type TezTransfer, + type TokenTransfer, +} from "@umami/core"; +import { type Address } from "@umami/tezos"; +import { CustomError } from "@umami/utils"; +import { FormProvider } from "react-hook-form"; + +import { SignButton } from "./SignButton"; +import { SignPageFee } from "./SignPageFee"; +import { SignPageHeader } from "./SignPageHeader"; +import { type LocalSignPageProps, useSignPageHelpers } from "./utils"; +import { AddressTile } from "../AddressTile"; +import { AdvancedSettingsAccordion } from "../AdvancedSettingsAccordion"; +import { TezTile, TokenTile } from "../AssetTiles"; + +export const LocalSignPage = (props: LocalSignPageProps) => { + const { operations: initialOperations, token, operationType } = props; + const { fee, operations, estimationFailed, isLoading, form, signer, onSign } = + useSignPageHelpers(initialOperations); + const hideBalance = useBreakpointValue({ base: true, md: false }); + + const operation = operations.operations[0]; + + const fields: Record = {}; + + switch (operationType) { + case "tez": + fields["mutezAmount"] = (operation as TezTransfer).amount; + fields["to"] = (operation as TezTransfer).recipient; + fields["from"] = operations.sender.address; + break; + case "token": + if (!token) { + throw new CustomError("Token is required for token operation"); + } + fields["amount"] = (operation as TokenTransfer).amount; + fields["to"] = (operation as TokenTransfer).recipient; + fields["from"] = operations.sender.address; + fields["token"] = token; + break; + } + + const AddressLabelAndTile = (heading: string, address: Address | undefined) => { + if (!address) { + return null; + } + return ( + + {heading} + + + ); + }; + + const Fee = () => ( + + + + ); + + const MutezAndFee = (mutezAmount: string | undefined) => { + if (!mutezAmount) { + return null; + } + return ( + + Amount + + + + ); + }; + + const TokenAndFee = (token: FA12TokenBalance | FA2TokenBalance | undefined, amount: string) => { + if (!token) { + return null; + } + return ( + + Amount + + + + ); + }; + + const formFields = { + mutezAmount: MutezAndFee(fields["mutezAmount"]), + from: AddressLabelAndTile("From", fields["from"]), + to: AddressLabelAndTile("To", fields["to"]), + token: TokenAndFee(fields["token"], fields["amount"]), + }; + + const renderField = (key: keyof typeof formFields) => formFields[key]; + + return ( + + +
+ + + + + {renderField("mutezAmount")} + {renderField("token")} + {renderField("from")} + {renderField("to")} + + + + + + + + +
+
+ ); +}; diff --git a/apps/web/src/components/SendFlow/Stake/FormPage.tsx b/apps/web/src/components/SendFlow/Stake/FormPage.tsx deleted file mode 100644 index b658095ef4..0000000000 --- a/apps/web/src/components/SendFlow/Stake/FormPage.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { - Center, - FormControl, - FormErrorMessage, - FormLabel, - Heading, - Input, - InputGroup, - InputRightElement, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, -} from "@chakra-ui/react"; -import hj from "@hotjar/browser"; -import { type Stake } from "@umami/core"; -import { type RawPkh, TEZ, TEZ_DECIMALS, parsePkh, tezToMutez } from "@umami/tezos"; -import { FormProvider, useForm } from "react-hook-form"; - -import { SignPage } from "./SignPage"; -import { AddressTile } from "../../AddressTile/AddressTile"; -import { - useAddToBatchFormAction, - useHandleOnSubmitFormActions, - useOpenSignPageFormAction, -} from "../onSubmitFormActionHooks"; -import { - type FormPageProps, - FormSubmitButton, - formDefaultValues, - getSmallestUnit, - makeValidateDecimals, -} from "../utils"; - -type FormValues = { - sender: RawPkh; - prettyAmount: string; -}; - -// TODO: test -export const FormPage = (props: FormPageProps) => { - const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: undefined, - FormPage, - defaultFormPageProps: props, - toOperation, - }); - - const addToBatch = useAddToBatchFormAction(toOperation); - - const { - onFormSubmitActionHandlers: [onSingleSubmit], - isLoading, - } = useHandleOnSubmitFormActions([openSignPage, addToBatch]); - - const form = useForm({ - mode: "onBlur", - defaultValues: formDefaultValues(props), - }); - const { - formState: { errors }, - register, - handleSubmit, - } = form; - - hj.stateChange("send_flow/stake_form_page"); - - return ( - - -
- -
- Select amount -
- -
- - From - - - - Enter amount - - - {TEZ} - - {errors.prettyAmount && ( - - {errors.prettyAmount.message} - - )} - - - - - -
-
-
- ); -}; - -const toOperation = (formValues: FormValues): Stake => ({ - type: "stake", - amount: tezToMutez(formValues.prettyAmount).toFixed(), - sender: parsePkh(formValues.sender), -}); diff --git a/apps/web/src/components/SendFlow/Stake/NoticeModal.tsx b/apps/web/src/components/SendFlow/Stake/NoticeModal.tsx deleted file mode 100644 index 35a80c2402..0000000000 --- a/apps/web/src/components/SendFlow/Stake/NoticeModal.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - Button, - Center, - Checkbox, - Heading, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - Text, -} from "@chakra-ui/react"; -import { useDynamicModalContext } from "@umami/components"; -import { type Account } from "@umami/core"; -import { useForm } from "react-hook-form"; - -import { FormPage } from "./FormPage"; -import { StubIcon as WarningIcon } from "../../../assets/icons"; -import { useColor } from "../../../styles/useColor"; - -export const NoticeModal = ({ account }: { account: Account }) => { - const { openWith } = useDynamicModalContext(); - const { setValue, watch } = useForm<{ consent: boolean }>({ - mode: "onBlur", - defaultValues: { consent: false }, - }); - const color = useColor(); - - const isConsentGiven = !watch("consent"); - - return ( - - - -
- - Disclaimer -
-
- -
- - Staked balances are locked in your account until they are manually unstaked and - finalized. You need to wait 4 cycles to finalize after an unstake. - - - - Staked funds are at risk. You might lose a portion of your stake if the chosen baker is - slashed for not following Tezos consensus mechanism rules. - - - setValue("consent", event.target.checked)} - paddingX="50px" - paddingY="25px" - > - I understand and accept the risks. - - - -
-
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Stake/SignPage.tsx b/apps/web/src/components/SendFlow/Stake/SignPage.tsx deleted file mode 100644 index 422693a8f6..0000000000 --- a/apps/web/src/components/SendFlow/Stake/SignPage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Stake } from "@umami/core"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { TezTile } from "../../AssetTiles/TezTile"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -// TODO: test -export const SignPage = (props: SignPageProps) => { - const { operations } = props; - const { isLoading, form, signer, onSign, fee } = useSignPageHelpers(operations); - - const { amount: mutezAmount } = operations.operations[0] as Stake; - - return ( - - -
- - - From - - - - - - - Stake amount - - - - - - - - -
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Tez/FormPage.test.tsx b/apps/web/src/components/SendFlow/Tez/FormPage.test.tsx index bad1e4d215..ae2941aeac 100644 --- a/apps/web/src/components/SendFlow/Tez/FormPage.test.tsx +++ b/apps/web/src/components/SendFlow/Tez/FormPage.test.tsx @@ -17,7 +17,6 @@ import { CustomError } from "@umami/utils"; import { BigNumber } from "bignumber.js"; import { FormPage } from "./FormPage"; -import { SignPage } from "./SignPage"; import { act, dynamicModalContextMock, @@ -27,6 +26,7 @@ import { userEvent, waitFor, } from "../../../testUtils"; +import { LocalSignPage } from "../LocalSignPage"; jest.mock("@umami/core", () => ({ ...jest.requireActual("@umami/core"), @@ -262,10 +262,9 @@ describe("
", () => { await act(() => user.click(submitButton)); expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - ); diff --git a/apps/web/src/components/SendFlow/Tez/FormPage.tsx b/apps/web/src/components/SendFlow/Tez/FormPage.tsx index a36dc0dd32..a29f662c78 100644 --- a/apps/web/src/components/SendFlow/Tez/FormPage.tsx +++ b/apps/web/src/components/SendFlow/Tez/FormPage.tsx @@ -15,7 +15,6 @@ import { useGetAccountBalanceDetails } from "@umami/state"; import { type RawPkh, TEZ, TEZ_DECIMALS, parsePkh, tezToMutez } from "@umami/tezos"; import { FormProvider, useForm } from "react-hook-form"; -import { SignPage } from "./SignPage"; import { useColor } from "../../../styles/useColor"; import { KnownAccountsAutocomplete } from "../../AddressAutocomplete"; import { TezTile } from "../../AssetTiles"; @@ -42,8 +41,7 @@ export type FormValues = { export const FormPage = ({ ...props }: FormPageProps) => { const color = useColor(); const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: undefined, + operationType: "tez", FormPage, defaultFormPageProps: props, toOperation, diff --git a/apps/web/src/components/SendFlow/Tez/SignPage.test.tsx b/apps/web/src/components/SendFlow/Tez/SignPage.test.tsx deleted file mode 100644 index 04e15a667d..0000000000 --- a/apps/web/src/components/SendFlow/Tez/SignPage.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Modal } from "@chakra-ui/react"; -import { makeAccountOperations, mockImplicitAccount, mockMnemonicAccount } from "@umami/core"; -import { addTestAccount, makeStore } from "@umami/state"; -import { executeParams } from "@umami/test-utils"; -import { TEZ, mockImplicitAddress } from "@umami/tezos"; - -import { SignPage } from "./SignPage"; -import { render, screen, waitFor } from "../../../testUtils"; -import { type SignPageProps } from "../utils"; - -jest.mock("@chakra-ui/react", () => ({ - ...jest.requireActual("@chakra-ui/react"), - useBreakpointValue: jest.fn(), -})); - -const fixture = (props: SignPageProps) => ( - {}}> - - -); - -describe("", () => { - describe("fee", () => { - it("displays the fee in tez", async () => { - const store = makeStore(); - addTestAccount(store, mockMnemonicAccount(0)); - const props: SignPageProps = { - operations: { - ...makeAccountOperations(mockImplicitAccount(0), mockImplicitAccount(0), [ - { - type: "tez", - amount: "1000000", - recipient: mockImplicitAddress(1), - }, - ]), - estimates: [executeParams({ fee: 1234567 })], - }, - mode: "single", - data: undefined, - }; - render(fixture(props), { store }); - - await waitFor(() => expect(screen.getByTestId("fee")).toHaveTextContent(`1.234567 ${TEZ}`)); - }); - }); -}); diff --git a/apps/web/src/components/SendFlow/Tez/SignPage.tsx b/apps/web/src/components/SendFlow/Tez/SignPage.tsx deleted file mode 100644 index 444dc7e666..0000000000 --- a/apps/web/src/components/SendFlow/Tez/SignPage.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - Flex, - FormControl, - FormLabel, - ModalBody, - ModalContent, - ModalFooter, - useBreakpointValue, -} from "@chakra-ui/react"; -import { type TezTransfer } from "@umami/core"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { TezTile } from "../../AssetTiles/TezTile"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -export const SignPage = (props: SignPageProps) => { - const { operations: initialOperations } = props; - const { fee, operations, estimationFailed, isLoading, form, signer, onSign } = - useSignPageHelpers(initialOperations); - const hideBalance = useBreakpointValue({ base: true, md: false }); - - const { amount: mutezAmount, recipient } = operations.operations[0] as TezTransfer; - - return ( - - - - - - - Amount - - - - - - - From - - - - To - - - - - - - - - - - ); -}; diff --git a/apps/web/src/components/SendFlow/Token/FormPage.test.tsx b/apps/web/src/components/SendFlow/Token/FormPage.test.tsx index 11eba0205f..6fce4fd51b 100644 --- a/apps/web/src/components/SendFlow/Token/FormPage.test.tsx +++ b/apps/web/src/components/SendFlow/Token/FormPage.test.tsx @@ -13,7 +13,6 @@ import { executeParams } from "@umami/test-utils"; import { parseContractPkh } from "@umami/tezos"; import { FormPage, type FormValues } from "./FormPage"; -import { SignPage } from "./SignPage"; import { act, dynamicModalContextMock, @@ -23,6 +22,7 @@ import { userEvent, waitFor, } from "../../../testUtils"; +import { LocalSignPage } from "../LocalSignPage"; import { type FormPagePropsWithSender } from "../utils"; jest.mock("@umami/core", () => ({ @@ -241,11 +241,11 @@ describe("", () => { await act(() => user.click(submitButton)); expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - ); expect(mockToast).not.toHaveBeenCalled(); diff --git a/apps/web/src/components/SendFlow/Token/FormPage.tsx b/apps/web/src/components/SendFlow/Token/FormPage.tsx index 7439c3a95b..4d27d10d82 100644 --- a/apps/web/src/components/SendFlow/Token/FormPage.tsx +++ b/apps/web/src/components/SendFlow/Token/FormPage.tsx @@ -23,7 +23,6 @@ import { import { type RawPkh, parseContractPkh, parsePkh } from "@umami/tezos"; import { FormProvider, useForm } from "react-hook-form"; -import { SignPage } from "./SignPage"; import { KnownAccountsAutocomplete } from "../../AddressAutocomplete"; import { TokenTile } from "../../AssetTiles"; import { FormPageHeader } from "../FormPageHeader"; @@ -51,8 +50,8 @@ export const FormPage = ( ) => { const { token } = props; const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: { token }, + operationType: "token", + token, FormPage, defaultFormPageProps: props, toOperation: toOperation(token), diff --git a/apps/web/src/components/SendFlow/Token/SignPage.tsx b/apps/web/src/components/SendFlow/Token/SignPage.tsx deleted file mode 100644 index cae07bbcb6..0000000000 --- a/apps/web/src/components/SendFlow/Token/SignPage.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - Flex, - FormControl, - FormLabel, - ModalBody, - ModalContent, - ModalFooter, - useBreakpointValue, -} from "@chakra-ui/react"; -import { type FA12TokenBalance, type FA2TokenBalance, type TokenTransfer } from "@umami/core"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { TokenTile } from "../../AssetTiles"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -export const SignPage = (props: SignPageProps<{ token: FA12TokenBalance | FA2TokenBalance }>) => { - const { - operations: initialOperations, - data: { token }, - } = props; - const { fee, operations, estimationFailed, isLoading, form, signer, onSign } = - useSignPageHelpers(initialOperations); - const hideBalance = useBreakpointValue({ base: true, md: false }); - const { amount, recipient } = operations.operations[0] as TokenTransfer; - - return ( - - -
- - - - Amount - - - - - - - From - - - - To - - - - - - - - -
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Undelegation/FormPage.test.tsx b/apps/web/src/components/SendFlow/Undelegation/FormPage.test.tsx deleted file mode 100644 index d71e2b16a4..0000000000 --- a/apps/web/src/components/SendFlow/Undelegation/FormPage.test.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { - estimate, - makeAccountOperations, - mockImplicitAccount, - mockMnemonicAccount, - mockMultisigAccount, -} from "@umami/core"; -import { - type UmamiStore, - addTestAccounts, - assetsActions, - makeStore, - mockToast, -} from "@umami/state"; -import { executeParams } from "@umami/test-utils"; -import { mockImplicitAddress } from "@umami/tezos"; -import { CustomError } from "@umami/utils"; - -import { FormPage, type FormValues } from "./FormPage"; -import { SignPage } from "./SignPage"; -import { - act, - dynamicModalContextMock, - renderInModal, - screen, - userEvent, - waitFor, -} from "../../../testUtils"; -import { type FormPagePropsWithSender } from "../utils"; - -const fixture = (props: FormPagePropsWithSender) => ; - -let store: UmamiStore; - -beforeEach(() => { - store = makeStore(); -}); - -jest.mock("@umami/core", () => ({ - ...jest.requireActual("@umami/core"), - estimate: jest.fn(), -})); - -describe("
", () => { - describe("default values", () => { - it("renders a form with a prefilled sender", async () => { - await renderInModal(fixture({ sender: mockImplicitAccount(0) }), store); - - expect(screen.getAllByTestId("address-tile")[0]).toHaveTextContent( - mockImplicitAccount(0).address.pkh - ); - }); - - it("shows address tile for baker", async () => { - const sender = mockImplicitAccount(0); - const baker = mockImplicitAccount(1); - store.dispatch( - assetsActions.updateBakers([ - { address: baker.address.pkh, name: "baker1", stakingBalance: 1 }, - ]) - ); - - await renderInModal( - fixture({ - sender: sender, - form: { - sender: sender.address.pkh, - baker: baker.address.pkh, - }, - }), - store - ); - - expect(screen.getAllByTestId("address-tile")[1]).toHaveTextContent("baker1"); - }); - }); - - describe("single transaction", () => { - beforeEach(() => { - addTestAccounts(store, [mockMnemonicAccount(0), mockMultisigAccount(0)]); - }); - - it("shows a toast if estimation fails", async () => { - const user = userEvent.setup(); - await renderInModal( - fixture({ - sender: mockImplicitAccount(0), - form: { - sender: mockImplicitAccount(0).address.pkh, - baker: mockImplicitAccount(1).address.pkh, - }, - }), - store - ); - - const estimateMock = jest.mocked(estimate); - estimateMock.mockRejectedValue(new CustomError("Some error occurred")); - - const submitButton = screen.getByText("Preview"); - await waitFor(() => expect(submitButton).toBeEnabled()); - await act(() => user.click(submitButton)); - - expect(estimateMock).toHaveBeenCalledTimes(1); - expect(mockToast).toHaveBeenCalledWith({ - description: "Some error occurred", - status: "error", - isClosable: true, - }); - }); - - it("opens a sign page if estimation succeeds", async () => { - const user = userEvent.setup(); - const sender = mockImplicitAccount(0); - await renderInModal( - fixture({ - sender: sender, - form: { - sender: sender.address.pkh, - baker: mockImplicitAddress(2).pkh, - }, - }), - store - ); - const submitButton = screen.getByText("Preview"); - await waitFor(() => expect(submitButton).toBeEnabled()); - - const operations = { - ...makeAccountOperations(sender, sender, [ - { - type: "undelegation", - sender: sender.address, - }, - ]), - estimates: [executeParams()], - }; - - jest.mocked(estimate).mockResolvedValueOnce(operations); - - await act(() => user.click(submitButton)); - - expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - - ); - expect(mockToast).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/apps/web/src/components/SendFlow/Undelegation/FormPage.tsx b/apps/web/src/components/SendFlow/Undelegation/FormPage.tsx deleted file mode 100644 index bef9d2c1c1..0000000000 --- a/apps/web/src/components/SendFlow/Undelegation/FormPage.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { FormControl, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import hj from "@hotjar/browser"; -import { type Undelegation } from "@umami/core"; -import { type RawPkh, parsePkh } from "@umami/tezos"; -import { FormProvider, useForm } from "react-hook-form"; - -import { SignPage } from "./SignPage"; -import { OwnedAccountsAutocomplete } from "../../AddressAutocomplete"; -import { AddressTile } from "../../AddressTile/AddressTile"; -import { FormPageHeader } from "../FormPageHeader"; -import { - useAddToBatchFormAction, - useHandleOnSubmitFormActions, - useOpenSignPageFormAction, -} from "../onSubmitFormActionHooks"; -import { type FormPagePropsWithSender, FormSubmitButton, formDefaultValues } from "../utils"; - -export type FormValues = { - sender: RawPkh; - baker: RawPkh; -}; - -export const FormPage = (props: FormPagePropsWithSender) => { - const { sender } = props; - // it must always be passed in from the parent component - const baker = props.form?.baker; - - const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: undefined, - FormPage, - defaultFormPageProps: { sender }, - toOperation, - }); - - const addToBatch = useAddToBatchFormAction(toOperation); - - const { - onFormSubmitActionHandlers: [onSingleSubmit], - isLoading, - } = useHandleOnSubmitFormActions([openSignPage, addToBatch]); - - const form = useForm({ - mode: "onBlur", - defaultValues: formDefaultValues({ sender }), - }); - - const { handleSubmit } = form; - - hj.stateChange("send_flow/undelegation_form_page"); - - return ( - - - - - - - - - - Baker - {baker && } - - - - - - - - ); -}; - -const toOperation = (formValues: FormValues): Undelegation => ({ - type: "undelegation", - sender: parsePkh(formValues.sender), -}); diff --git a/apps/web/src/components/SendFlow/Undelegation/SignPage.test.tsx b/apps/web/src/components/SendFlow/Undelegation/SignPage.test.tsx deleted file mode 100644 index d48227cbd1..0000000000 --- a/apps/web/src/components/SendFlow/Undelegation/SignPage.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Modal } from "@chakra-ui/react"; -import { makeAccountOperations, mockImplicitAccount, mockMnemonicAccount } from "@umami/core"; -import { type UmamiStore, addTestAccount, makeStore } from "@umami/state"; -import { executeParams } from "@umami/test-utils"; -import { TEZ } from "@umami/tezos"; - -import { SignPage } from "./SignPage"; -import { render, screen, waitFor } from "../../../testUtils"; -import { type SignPageProps } from "../utils"; - -const fixture = (props: SignPageProps) => ( - {}}> - - -); - -let store: UmamiStore; - -beforeEach(() => { - store = makeStore(); - addTestAccount(store, mockMnemonicAccount(0)); -}); - -describe("", () => { - const sender = mockImplicitAccount(0); - const operations = { - ...makeAccountOperations(sender, mockImplicitAccount(0), [ - { - type: "undelegation", - sender: sender.address, - }, - ]), - estimates: [ - executeParams({ - fee: 1234567, - }), - ], - }; - - describe("fee", () => { - it("displays the fee in tez", async () => { - const props: SignPageProps = { - operations, - mode: "single", - data: undefined, - }; - render(fixture(props), { store }); - - await waitFor(() => expect(screen.getByTestId("fee")).toHaveTextContent(`1.234567 ${TEZ}`)); - }); - }); -}); diff --git a/apps/web/src/components/SendFlow/Undelegation/SignPage.tsx b/apps/web/src/components/SendFlow/Undelegation/SignPage.tsx deleted file mode 100644 index b4a4689c4e..0000000000 --- a/apps/web/src/components/SendFlow/Undelegation/SignPage.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; - -export const SignPage = (props: SignPageProps) => { - const { operations: initialOperations } = props; - - const { fee, estimationFailed, isLoading, form, signer, onSign } = - useSignPageHelpers(initialOperations); - return ( - - -
- - - From - - - - - - - - - - - - -
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Unstake/FormPage.tsx b/apps/web/src/components/SendFlow/Unstake/FormPage.tsx deleted file mode 100644 index 8c4e049e62..0000000000 --- a/apps/web/src/components/SendFlow/Unstake/FormPage.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { - Center, - FormControl, - FormErrorMessage, - FormLabel, - Heading, - Input, - InputGroup, - InputRightElement, - ModalBody, - ModalCloseButton, - ModalContent, - ModalFooter, - ModalHeader, -} from "@chakra-ui/react"; -import hj from "@hotjar/browser"; -import { type Unstake } from "@umami/core"; -import { type RawPkh, TEZ, TEZ_DECIMALS, parsePkh, tezToMutez } from "@umami/tezos"; -import { BigNumber } from "bignumber.js"; -import { FormProvider, useForm } from "react-hook-form"; - -import { SignPage } from "./SignPage"; -import { TezTile } from "../../AssetTiles/TezTile"; -import { - useAddToBatchFormAction, - useHandleOnSubmitFormActions, - useOpenSignPageFormAction, -} from "../onSubmitFormActionHooks"; -import { - type FormPageProps, - FormSubmitButton, - formDefaultValues, - getSmallestUnit, - makeValidateDecimals, -} from "../utils"; - -type FormValues = { - sender: RawPkh; - prettyAmount: string; -}; - -// TODO: test -export const FormPage = (props: FormPageProps & { stakedBalance: number }) => { - const stakedBalance = props.stakedBalance; - - const openSignPage = useOpenSignPageFormAction({ - SignPage, - signPageExtraData: { stakedBalance }, - FormPage, - defaultFormPageProps: props, - toOperation, - }); - - const addToBatch = useAddToBatchFormAction(toOperation); - - const { - onFormSubmitActionHandlers: [onSingleSubmit], - isLoading, - } = useHandleOnSubmitFormActions([openSignPage, addToBatch]); - - const form = useForm({ - mode: "onBlur", - defaultValues: formDefaultValues(props), - }); - const { - formState: { errors }, - register, - handleSubmit, - } = form; - - hj.stateChange("send_flow/unstake_form_page"); - - return ( - - -
- -
- Select amount -
- -
- - Stake amount - - - - Enter amount - - { - if (tezToMutez(value).gt(BigNumber(stakedBalance))) { - return "Amount must be less than or equal to the staked balance"; - } - return makeValidateDecimals(TEZ_DECIMALS)(value); - }, - })} - placeholder="0.000000" - /> - {TEZ} - - {errors.prettyAmount && ( - - {errors.prettyAmount.message} - - )} - - - - - -
-
-
- ); -}; - -const toOperation = (formValues: FormValues): Unstake => ({ - type: "unstake", - amount: tezToMutez(formValues.prettyAmount).toFixed(), - sender: parsePkh(formValues.sender), -}); diff --git a/apps/web/src/components/SendFlow/Unstake/NoticeModal.tsx b/apps/web/src/components/SendFlow/Unstake/NoticeModal.tsx deleted file mode 100644 index fb62667f81..0000000000 --- a/apps/web/src/components/SendFlow/Unstake/NoticeModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { - Button, - Center, - Heading, - ModalBody, - ModalCloseButton, - ModalContent, - ModalHeader, - Text, -} from "@chakra-ui/react"; -import { useDynamicModalContext } from "@umami/components"; -import { type Account } from "@umami/core"; -import { useGetAccountStakedBalance } from "@umami/state"; - -import { FormPage } from "./FormPage"; -import { StubIcon as WarningIcon } from "../../../assets/icons"; -import { useColor } from "../../../styles/useColor"; -import { NoticeSteps } from "../NoticeSteps"; -// TODO: test -export const NoticeModal = ({ account }: { account: Account }) => { - const color = useColor(); - const { openWith } = useDynamicModalContext(); - const stakedBalance = useGetAccountStakedBalance(account.address.pkh); - - return ( - - - -
- - Important notice -
-
- -
- - After submitting an unstake, the chosen amount will become finalizable after 4 cycles - (~10 days). Then, you will need to finalize unstaked balances in order to make them - spendable. - - - - - -
-
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/Unstake/SignPage.tsx b/apps/web/src/components/SendFlow/Unstake/SignPage.tsx deleted file mode 100644 index dc23a36692..0000000000 --- a/apps/web/src/components/SendFlow/Unstake/SignPage.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Unstake } from "@umami/core"; -import { FormProvider } from "react-hook-form"; - -import { AddressTile } from "../../AddressTile/AddressTile"; -import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { TezTile } from "../../AssetTiles/TezTile"; -import { SignButton } from "../SignButton"; -import { SignPageFee } from "../SignPageFee"; -import { SignPageHeader } from "../SignPageHeader"; -import { type SignPageProps, useSignPageHelpers } from "../utils"; -// TODO: test -export const SignPage = (props: SignPageProps<{ stakedBalance: number }>) => { - const { - operations, - data: { stakedBalance }, - } = props; - const { isLoading, form, signer, onSign, fee } = useSignPageHelpers(operations); - const { amount: mutezAmount } = operations.operations[0] as Unstake; - - return ( - - -
- - - From - - - - - - - Stake amount - - - Amount to unstake - - - - - - - - -
-
- ); -}; diff --git a/apps/web/src/components/SendFlow/common/RequestSignPage.test.tsx b/apps/web/src/components/SendFlow/common/RequestSignPage.test.tsx index 89f3c63d0d..cb3be67327 100644 --- a/apps/web/src/components/SendFlow/common/RequestSignPage.test.tsx +++ b/apps/web/src/components/SendFlow/common/RequestSignPage.test.tsx @@ -39,7 +39,7 @@ import { } from "../../../testUtils"; import { SuccessStep } from "../SuccessStep"; import { type SdkSignPageProps, type SignHeaderProps } from "../utils"; -import { SingleSignPage } from "./RequestSignPage"; +import { RequestSignPage } from "./RequestSignPage"; jest.mock("@umami/core", () => ({ ...jest.requireActual("@umami/core"), @@ -89,7 +89,7 @@ const operation: EstimatedAccountOperations = { estimates: [executeParams({ fee: 123 })], }; -describe("", () => { +describe("", () => { const store = makeStore(); const user = userEvent.setup(); @@ -125,7 +125,7 @@ describe("", () => { jest.mocked(executeOperations).mockResolvedValue({ opHash: "ophash" } as BatchWalletOperation); - await renderInModal(, store); + await renderInModal(, store); expect(screen.getByText("Ghostnet")).toBeInTheDocument(); expect(screen.queryByText("Mainnet")).not.toBeInTheDocument(); @@ -244,7 +244,7 @@ describe("batch handling", () => { jest.mocked(executeOperations).mockResolvedValue({ opHash: "ophash" } as BatchWalletOperation); jest.spyOn(WalletClient, "respond").mockResolvedValue(); - await renderInModal(, store); + await renderInModal(, store); expect(screen.getByText("Ghostnet")).toBeInTheDocument(); expect(screen.queryByText("Mainnet")).not.toBeInTheDocument(); diff --git a/apps/web/src/components/SendFlow/common/RequestSignPage.tsx b/apps/web/src/components/SendFlow/common/RequestSignPage.tsx index e76ded8e1e..1344fc2eed 100644 --- a/apps/web/src/components/SendFlow/common/RequestSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/RequestSignPage.tsx @@ -40,7 +40,7 @@ import { SignPageFee } from "../SignPageFee"; import { type SdkSignPageProps } from "../utils"; import { useSignWithWalletConnect } from "../WalletConnect/useSignWithWalletConnect"; -export const SingleSignPage = (signProps: SdkSignPageProps) => { +export const RequestSignPage = (signProps: SdkSignPageProps) => { const color = useColor(); const totalFinalizableAmount = useAccountTotalFinalizableUnstakeAmount( diff --git a/apps/web/src/components/SendFlow/onSubmitFormActionHooks.tsx b/apps/web/src/components/SendFlow/onSubmitFormActionHooks.tsx index 7d290ac354..3d65a716c8 100644 --- a/apps/web/src/components/SendFlow/onSubmitFormActionHooks.tsx +++ b/apps/web/src/components/SendFlow/onSubmitFormActionHooks.tsx @@ -1,6 +1,12 @@ import { useToast } from "@chakra-ui/react"; import { useDynamicModalContext } from "@umami/components"; -import { type EstimatedAccountOperations, type Operation, estimate } from "@umami/core"; +import { + type EstimatedAccountOperations, + type FA12TokenBalance, + type FA2TokenBalance, + type Operation, + estimate, +} from "@umami/core"; import { estimateAndUpdateBatch, useAppDispatch, @@ -9,12 +15,8 @@ import { } from "@umami/state"; import { type FunctionComponent } from "react"; -import { - type BaseFormValues, - type FormPageProps, - type SignPageProps, - useMakeFormOperations, -} from "./utils"; +import { LocalSignPage } from "./LocalSignPage"; +import { type BaseFormValues, type FormPageProps, useMakeFormOperations } from "./utils"; // This file defines hooks to create actions when form is submitted. @@ -23,14 +25,11 @@ type OnSubmitFormAction = ( ) => Promise; type UseOpenSignPageArgs< - ExtraData, FormValues extends BaseFormValues, FormProps extends FormPageProps, > = { - // Sign page component to render. - SignPage: FunctionComponent>; - // Extra data to pass to the Sign page component (e.g. NFT or Token) - signPageExtraData: ExtraData; + operationType: "token" | "tez"; + token?: FA12TokenBalance | FA2TokenBalance; // Form page component to render when the user goes back from the sign page. FormPage: FunctionComponent; // Form page props, used to render the form page again when the user goes back from the sign page @@ -42,16 +41,15 @@ type UseOpenSignPageArgs< // Hook to open the sign page that knows how to get back to the form page. export const useOpenSignPageFormAction = < - SignPageData, FormValues extends BaseFormValues, FormProps extends FormPageProps, >({ - SignPage, - signPageExtraData, + operationType, + token, FormPage, defaultFormPageProps, toOperation, -}: UseOpenSignPageArgs): OnSubmitFormAction => { +}: UseOpenSignPageArgs): OnSubmitFormAction => { const { openWith } = useDynamicModalContext(); const makeFormOperations = useMakeFormOperations(toOperation); const network = useSelectedNetwork(); @@ -61,8 +59,7 @@ export const useOpenSignPageFormAction = < const estimatedOperations = await estimate(operations, network); return openWith( - openWith( ) } - mode="single" + operationType={operationType} operations={estimatedOperations} + token={token} /> ); }; diff --git a/apps/web/src/components/SendFlow/utils.tsx b/apps/web/src/components/SendFlow/utils.tsx index 6c5961a1f3..13be353432 100644 --- a/apps/web/src/components/SendFlow/utils.tsx +++ b/apps/web/src/components/SendFlow/utils.tsx @@ -6,6 +6,8 @@ import { type Account, type AccountOperations, type EstimatedAccountOperations, + type FA12TokenBalance, + type FA2TokenBalance, type ImplicitAccount, type Operation, estimate, @@ -45,6 +47,13 @@ export type BaseFormValues = { sender: RawPkh }; export type SignPageMode = "single" | "batch"; +export type LocalSignPageProps = { + goBack?: () => void; + operationType: "token" | "tez"; + token?: FA12TokenBalance | FA2TokenBalance; + operations: EstimatedAccountOperations; +}; + export type SignPageProps = { goBack?: () => void; operations: EstimatedAccountOperations; diff --git a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx index e1dbe80d63..b0199b7e3d 100644 --- a/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx +++ b/apps/web/src/components/WalletConnect/useHandleWcRequest.tsx @@ -21,7 +21,7 @@ import { formatJsonRpcError, formatJsonRpcResult } from "@walletconnect/jsonrpc- import { type SessionTypes, type SignClientTypes, type Verify } from "@walletconnect/types"; import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal"; -import { SingleSignPage } from "../SendFlow/common/RequestSignPage"; +import { RequestSignPage } from "../SendFlow/common/RequestSignPage"; import { type SdkSignPageProps, type SignHeaderProps, @@ -184,7 +184,7 @@ export const useHandleWcRequest = () => { operation: estimatedOperations, }; - modal = ; + modal = ; onClose = () => { handleUserRejected(); diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx index 148e709e31..49fc37c055 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx @@ -21,7 +21,7 @@ import { CustomError } from "@umami/utils"; import { useHandleBeaconMessage } from "./useHandleBeaconMessage"; import { act, dynamicModalContextMock, renderHook, screen, waitFor } from "../../testUtils"; -import { SingleSignPage } from "../SendFlow/common/RequestSignPage"; +import { RequestSignPage } from "../SendFlow/common/RequestSignPage"; import { type SdkSignPageProps, type SignHeaderProps } from "../SendFlow/utils"; jest.mock("@umami/core", () => ({ @@ -352,7 +352,7 @@ describe("", () => { }); describe("single operation", () => { - it("opens a modal with the SingleSignPage for 1 operation", async () => { + it("opens a modal with the RequestSignPage for 1 operation", async () => { jest.mocked(estimate).mockResolvedValueOnce({ ...makeAccountOperations(account, account, [ { type: "tez", amount: "1", recipient: mockImplicitAddress(2) }, @@ -400,7 +400,7 @@ describe("", () => { await waitFor(() => expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - , + , { onClose: expect.any(Function) } ) ); @@ -510,7 +510,7 @@ describe("", () => { await waitFor(() => expect(dynamicModalContextMock.openWith).toHaveBeenCalledWith( - , + , { onClose: expect.any(Function) } ) ); diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx index 53871e4c53..fc3cd216d1 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.tsx @@ -19,7 +19,7 @@ import { BeaconError, CustomError, getErrorContext } from "@umami/utils"; import { PermissionRequestModal } from "./PermissionRequestModal"; import { SignPayloadRequestModal } from "../common/SignPayloadRequestModal"; -import { SingleSignPage } from "../SendFlow/common/RequestSignPage"; +import { RequestSignPage } from "../SendFlow/common/RequestSignPage"; import { type SdkSignPageProps, type SignHeaderProps, @@ -30,7 +30,7 @@ import { * @returns a function that handles a beacon message and opens a modal with the appropriate content * * For operation requests it will also try to convert the operation(s) to our {@link Operation} format, - * estimate the fee and open the SingleSignPage only if it succeeds + * estimate the fee and open the RequestSignPage only if it succeeds */ export const useHandleBeaconMessage = () => { const { openWith, isOpen, canBeOverridden } = useDynamicModalContext(); @@ -147,7 +147,7 @@ export const useHandleBeaconMessage = () => { operation: estimatedOperations, }; - modal = ; + modal = ; onClose = () => respondWithError(message.id, BeaconErrorType.ABORTED_ERROR);