Skip to content

Commit

Permalink
Merge pull request #1383 from trilitech/fix-beacon-signing
Browse files Browse the repository at this point in the history
Fix beacon signing
  • Loading branch information
serjonya-trili authored Jun 19, 2024
2 parents 7ff4dad + 10e70c2 commit 76fcc96
Show file tree
Hide file tree
Showing 12 changed files with 572 additions and 366 deletions.
47 changes: 34 additions & 13 deletions apps/desktop/src/components/DynamicModal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Modal, ModalOverlay, type ModalProps, useDisclosure } from "@chakra-ui/react";
import { type ReactElement, createContext, useState } from "react";
import { Modal, ModalOverlay, type ThemingProps, useDisclosure } from "@chakra-ui/react";
import { type ReactElement, createContext, useCallback, useState } from "react";
import { RemoveScroll } from "react-remove-scroll";

// this should be used in components as useContext(DynamicModalContext);
export const DynamicModalContext = createContext<{
openWith: (content: ReactElement, size?: ModalProps["size"]) => Promise<void>;
openWith: (
content: ReactElement,
props?: ThemingProps & { onClose?: () => void | Promise<void> }
) => Promise<void>;
onClose: () => void;
isOpen: boolean;
}>({
Expand All @@ -23,16 +26,35 @@ export const DynamicModalContext = createContext<{
// This hook should be used only once in the app. You place the modal wrapper and then
// use the `openWith` provided by the `DynamicModalContext` in components.
export const useDynamicModal = () => {
const { isOpen, onClose, onOpen } = useDisclosure();
const { isOpen, onClose: closeModal, onOpen } = useDisclosure();
const [modalContent, setModalContent] = useState<ReactElement | null>(null);
const [size, setSize] = useState<ModalProps["size"]>("md");
const [modalProps, setModalProps] = useState<
ThemingProps & { onClose: () => void | Promise<void> }
>({
size: "md",
onClose: closeModal,
});

const openWith = async (content: ReactElement, size: ModalProps["size"] = "md") => {
setSize(size);
setModalContent(content);
onOpen();
return Promise.resolve();
};
const onClose = useCallback(() => {
closeModal();
}, [closeModal]);

const openWith = useCallback(
async (
content: ReactElement,
props: ThemingProps & { onClose?: () => void | Promise<void> } = {}
) => {
const onClose = () => {
closeModal();
return props.onClose?.();
};
setModalProps({ size: "md", ...props, onClose });
setModalContent(content);
onOpen();
return Promise.resolve();
},
[onOpen, closeModal]
);

return {
isOpen,
Expand All @@ -47,8 +69,7 @@ export const useDynamicModal = () => {
isOpen={isOpen}
// this is used in e2e tests to decrease flakiness due to animations
motionPreset={(localStorage.getItem("chakra-modal-motion-preset") as any) || undefined}
onClose={onClose}
size={size}
{...modalProps}
>
<ModalOverlay />
<RemoveScroll enabled={isOpen}>{modalContent}</RemoveScroll>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const BatchSignPage: React.FC<BeaconSignPageProps> = ({ operation, messag
<Header message={message} mode="batch" operation={operation} />

<ModalBody>
<Accordion allowToggle={true}>
<Accordion allowToggle>
<AccordionItem background={colors.gray[800]} border="none" borderRadius="8px">
<AccordionButton>
<Heading flex="1" textAlign="left" paddingY="6px" size="sm">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { FormProvider } from "react-hook-form";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
Expand All @@ -11,35 +12,37 @@ import { SignPageFee } from "../SignPageFee";
import { headerText } from "../SignPageHeader";

export const FinalizeUnstakeSignPage: React.FC<BeaconSignPageProps> = ({ operation, message }) => {
const { isSigning, onSign, network, fee } = useSignWithBeacon(operation, message);
const { isSigning, onSign, network, fee, form } = useSignWithBeacon(operation, message);
const totalFinalizableAmount = useAccountTotalFinalizableUnstakeAmount(
operation.signer.address.pkh
);
return (
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>

<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />
<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />

<FormLabel marginTop="24px">Finalize Unstake</FormLabel>
<TezTile mutezAmount={totalFinalizableAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
<FormLabel marginTop="24px">Finalize Unstake</FormLabel>
<TezTile mutezAmount={totalFinalizableAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
</FormProvider>
);
};
51 changes: 27 additions & 24 deletions apps/desktop/src/components/SendFlow/Beacon/StakeSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { FormProvider } from "react-hook-form";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
Expand All @@ -13,33 +14,35 @@ import { headerText } from "../SignPageHeader";
export const StakeSignPage: React.FC<BeaconSignPageProps> = ({ operation, message }) => {
const { amount: mutezAmount } = operation.operations[0] as Stake;

const { isSigning, onSign, network, fee } = useSignWithBeacon(operation, message);
const { isSigning, onSign, network, fee, form } = useSignWithBeacon(operation, message);

return (
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>

<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />
<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />

<FormLabel marginTop="24px">Stake</FormLabel>
<TezTile mutezAmount={mutezAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
<FormLabel marginTop="24px">Stake</FormLabel>
<TezTile mutezAmount={mutezAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
</FormProvider>
);
};
51 changes: 27 additions & 24 deletions apps/desktop/src/components/SendFlow/Beacon/UnstakeSignPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react";
import { FormProvider } from "react-hook-form";

import { type BeaconSignPageProps } from "./BeaconSignPageProps";
import { Header } from "./Header";
Expand All @@ -13,33 +14,35 @@ import { headerText } from "../SignPageHeader";
export const UnstakeSignPage: React.FC<BeaconSignPageProps> = ({ operation, message }) => {
const { amount: mutezAmount } = operation.operations[0] as Unstake;

const { isSigning, onSign, network, fee } = useSignWithBeacon(operation, message);
const { isSigning, onSign, network, fee, form } = useSignWithBeacon(operation, message);

return (
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>
<FormProvider {...form}>
<ModalContent>
<form>
<Header message={message} mode="single" operation={operation} />
<ModalBody>
<Flex alignItems="center" justifyContent="end" marginTop="12px">
<SignPageFee fee={fee} />
</Flex>

<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />
<FormLabel marginTop="24px">From</FormLabel>
<AddressTile address={operation.sender.address} />

<FormLabel marginTop="24px">Unstake</FormLabel>
<TezTile mutezAmount={mutezAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
<FormLabel marginTop="24px">Unstake</FormLabel>
<TezTile mutezAmount={mutezAmount} />
</ModalBody>
<ModalFooter>
<SignButton
isLoading={isSigning}
network={network}
onSubmit={onSign}
signer={operation.signer}
text={headerText(operation.type, "single")}
/>
</ModalFooter>
</form>
</ModalContent>
</FormProvider>
);
};
37 changes: 37 additions & 0 deletions apps/desktop/src/mocks/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type PropsWithChildren, createContext, useContext } from "react";

const ModalContext = createContext<{ onClose: () => void }>({ onClose: () => {} });

export const MockModal = ({
children,
isOpen,
onClose,
}: PropsWithChildren<{ isOpen: boolean; onClose: () => void }>) => (
<ModalContext.Provider value={{ onClose }}>
<div data-testid="mock-modal">{isOpen ? children : null}</div>
</ModalContext.Provider>
);

export const MockModalHeader = ({ children }: PropsWithChildren<object>) => (
<div id="modal-header">{children}</div>
);

export const MockModalContent = ({ children }: PropsWithChildren<object>) => (
<section aria-labelledby="modal-header" aria-modal role="dialog">
{children}
</section>
);

export const MockModalInnerComponent = ({ children }: PropsWithChildren<object>) => (
<div>{children}</div>
);

export const MockModalCloseButton = ({ children }: PropsWithChildren<object>) => {
const { onClose } = useContext(ModalContext);

return (
<button aria-label="Close" onClick={onClose} type="button">
{children}
</button>
);
};
26 changes: 7 additions & 19 deletions apps/desktop/src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import { TextDecoder, TextEncoder } from "util";
import { setupJestCanvasMock } from "jest-canvas-mock";
import failOnConsole from "jest-fail-on-console";
import MockDate from "mockdate";
import React from "react";

import {
MockModal,
MockModalCloseButton,
MockModalContent,
MockModalHeader,
MockModalInnerComponent,
} from "./mocks/modal";
import { mockUseToast } from "./mocks/toast";
import { accountsSlice } from "./utils/redux/slices/accountsSlice/accountsSlice";
import { announcementSlice } from "./utils/redux/slices/announcementSlice";
Expand Down Expand Up @@ -71,24 +77,6 @@ beforeEach(() => {
setupJestCanvasMock();
});

const MockModal = ({ children, isOpen }: any) =>
React.createElement("div", { "data-testid": "mock-modal" }, isOpen ? children : null);

const MockModalHeader = ({ children }: any) =>
React.createElement("header", { id: "modal-header" }, children);

const MockModalContent = ({ children }: any) =>
React.createElement(
"section",
{ role: "dialog", "aria-labelledby": "modal-header", "aria-modal": true },
children
);

const MockModalInnerComponent = ({ children }: any) => React.createElement("div", {}, children);

const MockModalCloseButton = ({ children }: any) =>
React.createElement("button", { "aria-label": "Close" }, children);

jest.mock("@chakra-ui/react", () => ({
...jest.requireActual("@chakra-ui/react"),
// Mock toast since it has an erratic behavior in RTL
Expand Down
14 changes: 1 addition & 13 deletions apps/desktop/src/utils/beacon/PermissionRequestModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
BeaconErrorType,
BeaconMessageType,
type BeaconResponseInputMessage,
type PermissionRequestOutput,
Expand Down Expand Up @@ -29,7 +28,6 @@ import type React from "react";
import { useContext } from "react";
import { FormProvider, useForm } from "react-hook-form";

import { useRemovePeerBySenderId } from "./beacon";
import { WalletClient } from "./WalletClient";
import { JsValueWrap } from "../../components/AccountDrawer/JsValueWrap";
import { OwnedImplicitAccountsAutocomplete } from "../../components/AddressAutocomplete";
Expand All @@ -53,16 +51,6 @@ export const PermissionRequestModal: React.FC<{
getValues,
formState: { errors, isValid },
} = form;
const removePeer = useRemovePeerBySenderId();

const onModalClose = () => {
void removePeer(request.senderId);
void WalletClient.respond({
id: request.id,
type: BeaconMessageType.Error,
errorType: BeaconErrorType.NOT_GRANTED_ERROR,
});
};

const grant = () =>
handleAsyncAction(async () => {
Expand Down Expand Up @@ -101,7 +89,7 @@ export const PermissionRequestModal: React.FC<{
</Text>
</Flex>
</ModalHeader>
<ModalCloseButton onClick={onModalClose} />
<ModalCloseButton />
<ModalBody data-testid="beacon-request-body">
<Flex
alignItems="center"
Expand Down
Loading

0 comments on commit 76fcc96

Please sign in to comment.