From 9ca413ebe86c0b4f375a570799e3d59a6bebbfc9 Mon Sep 17 00:00:00 2001 From: Diana Savatina Date: Fri, 24 Jan 2025 10:26:08 +0000 Subject: [PATCH] feat: hints in modals for Beacon and WC --- .../HintsAccordion/HintsAccordion.tsx | 45 +++++++++++++++++ .../src/components/HintsAccordion/index.tsx | 1 + .../SendFlow/common/ContractCallSignPage.tsx | 5 +- .../SendFlow/common/DelegationSignPage.tsx | 5 +- .../common/FinalizeUnstakeSignPage.tsx | 4 +- .../OriginationOperationSignPage.test.tsx | 1 + .../common/OriginationOperationSignPage.tsx | 5 +- .../SendFlow/common/SingleSignPage.test.tsx | 25 +++++----- .../SendFlow/common/StakeSignPage.tsx | 5 +- .../SendFlow/common/TezSignPage.tsx | 5 +- .../SendFlow/common/UndelegationSignPage.tsx | 4 +- .../SendFlow/common/UnstakeSignPage.tsx | 5 +- .../beacon/useHandleBeaconMessage.test.tsx | 1 + packages/core/src/Hints.ts | 48 +++++++++++++++++++ .../Titles => packages/core/src}/Titles.tsx | 1 + packages/core/src/index.ts | 2 + 16 files changed, 134 insertions(+), 28 deletions(-) create mode 100644 apps/web/src/components/HintsAccordion/HintsAccordion.tsx create mode 100644 apps/web/src/components/HintsAccordion/index.tsx create mode 100644 packages/core/src/Hints.ts rename {apps/web/src/components/Titles => packages/core/src}/Titles.tsx (90%) diff --git a/apps/web/src/components/HintsAccordion/HintsAccordion.tsx b/apps/web/src/components/HintsAccordion/HintsAccordion.tsx new file mode 100644 index 0000000000..9685034a2e --- /dev/null +++ b/apps/web/src/components/HintsAccordion/HintsAccordion.tsx @@ -0,0 +1,45 @@ +import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, + Heading, +} from "@chakra-ui/react"; +import { Hints, type SignPage } from "@umami/core"; + +import { useColor } from "../../styles/useColor"; + +type HintsProps = { + signPage: SignPage; +}; + +export const HintsAccordion = ({ signPage }: HintsProps) => { + const color = useColor(); + + if (!Hints[signPage].header || !Hints[signPage].description) { + return null; + } + + return ( + + +

+ + + {Hints[signPage].header} + + + +

+ {Hints[signPage].description} +
+
+ ); +}; diff --git a/apps/web/src/components/HintsAccordion/index.tsx b/apps/web/src/components/HintsAccordion/index.tsx new file mode 100644 index 0000000000..b8dc309c60 --- /dev/null +++ b/apps/web/src/components/HintsAccordion/index.tsx @@ -0,0 +1 @@ +export * from "./HintsAccordion"; diff --git a/apps/web/src/components/SendFlow/common/ContractCallSignPage.tsx b/apps/web/src/components/SendFlow/common/ContractCallSignPage.tsx index fbfee6d104..f462b04194 100644 --- a/apps/web/src/components/SendFlow/common/ContractCallSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/ContractCallSignPage.tsx @@ -11,7 +11,7 @@ import { ModalContent, ModalFooter, } from "@chakra-ui/react"; -import { type ContractCall } from "@umami/core"; +import { type ContractCall, Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; @@ -19,8 +19,8 @@ import { useColor } from "../../../styles/useColor"; import { AddressTile } from "../../AddressTile/AddressTile"; import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; import { TezTile } from "../../AssetTiles/TezTile"; +import { HintsAccordion } from "../../HintsAccordion"; import { JsValueWrap } from "../../JsValueWrap"; -import { Titles } from "../../Titles/Titles"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -47,6 +47,7 @@ export const ContractCallSignPage = ({
+ diff --git a/apps/web/src/components/SendFlow/common/DelegationSignPage.tsx b/apps/web/src/components/SendFlow/common/DelegationSignPage.tsx index 83ff847bb5..d60bdd55d2 100644 --- a/apps/web/src/components/SendFlow/common/DelegationSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/DelegationSignPage.tsx @@ -1,11 +1,11 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Delegation } from "@umami/core"; +import { type Delegation, Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -27,6 +27,7 @@ export const DelegationSignPage = ({
+ From diff --git a/apps/web/src/components/SendFlow/common/FinalizeUnstakeSignPage.tsx b/apps/web/src/components/SendFlow/common/FinalizeUnstakeSignPage.tsx index d37cdbc668..bf525d714b 100644 --- a/apps/web/src/components/SendFlow/common/FinalizeUnstakeSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/FinalizeUnstakeSignPage.tsx @@ -1,11 +1,12 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; +import { Titles } from "@umami/core"; import { useAccountTotalFinalizableUnstakeAmount } from "@umami/state"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { TezTile } from "../../AssetTiles/TezTile"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -27,6 +28,7 @@ export const FinalizeUnstakeSignPage = ({
+ diff --git a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.test.tsx b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.test.tsx index 2a01e2b0cf..ed68d3ec12 100644 --- a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.test.tsx +++ b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.test.tsx @@ -65,6 +65,7 @@ describe("", () => { expect(screen.getByTestId("sign-page-header")).toHaveTextContent("Origination Request"); expect(screen.getByTestId("app-name")).toHaveTextContent("mockDappName"); + expect(screen.queryByTestId("hints-accordion")).not.toBeInTheDocument(); }); it("passes correct payload to sign handler", async () => { diff --git a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx index ac67f18ecf..20712fd0f4 100644 --- a/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/OriginationOperationSignPage.tsx @@ -10,17 +10,17 @@ import { ModalContent, ModalFooter, } from "@chakra-ui/react"; -import { type ContractOrigination } from "@umami/core"; +import { type ContractOrigination, Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { useColor } from "../../../styles/useColor"; import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; +import { HintsAccordion } from "../../HintsAccordion"; import { JsValueWrap } from "../../JsValueWrap"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; import { Header } from "./Header"; -import { Titles } from "../../Titles/Titles"; export const OriginationOperationSignPage = ({ operation, @@ -39,6 +39,7 @@ export const OriginationOperationSignPage = ({
+ diff --git a/apps/web/src/components/SendFlow/common/SingleSignPage.test.tsx b/apps/web/src/components/SendFlow/common/SingleSignPage.test.tsx index 945edb8ae4..9998c30beb 100644 --- a/apps/web/src/components/SendFlow/common/SingleSignPage.test.tsx +++ b/apps/web/src/components/SendFlow/common/SingleSignPage.test.tsx @@ -2,7 +2,10 @@ import { BeaconMessageType, NetworkType, type OperationRequestOutput } from "@ai import type { BatchWalletOperation } from "@taquito/taquito/dist/types/wallet/batch-operation"; import { type EstimatedAccountOperations, + Hints, type Operation, + type SignPage, + Titles, executeOperations, mockContractCall, mockContractOrigination, @@ -29,7 +32,6 @@ import { import { SuccessStep } from "../SuccessStep"; import { type SdkSignPageProps, type SignHeaderProps } from "../utils"; import { SingleSignPage } from "./SingleSignPage"; -import { Titles } from "../../Titles/Titles"; jest.mock("@umami/core", () => ({ ...jest.requireActual("@umami/core"), @@ -60,7 +62,7 @@ describe("", () => { } as OperationRequestOutput; // check all types of Modals called by SingleSignOperation - const mockedOperations: Record = { + const mockedOperations: Record = { TezSignPage: mockTezOperation(0), ContractCallSignPage: mockContractCall(0), DelegationSignPage: mockDelegationOperation(0), @@ -71,17 +73,6 @@ describe("", () => { FinalizeUnstakeSignPage: mockFinalizeUnstakeOperation(0), }; - const titles: Record = { - TezSignPage: Titles.TezSignPage, - ContractCallSignPage: Titles.ContractCallSignPage, - DelegationSignPage: Titles.DelegationSignPage, - UndelegationSignPage: Titles.UndelegationSignPage, - OriginationOperationSignPage: Titles.OriginationOperationSignPage, - StakeSignPage: Titles.StakeSignPage, - UnstakeSignPage: Titles.UnstakeSignPage, - FinalizeUnstakeSignPage: Titles.FinalizeUnstakeSignPage, - }; - const operation: EstimatedAccountOperations = { type: "implicit" as const, sender: mockImplicitAccount(0), @@ -117,8 +108,14 @@ describe("", () => { expect(screen.queryByText("Mainnet")).not.toBeInTheDocument(); expect(screen.getByTestId(key)).toBeInTheDocument(); // e.g. TezSignPage - expect(screen.getByTestId("sign-page-header")).toHaveTextContent(titles[key]); + expect(screen.getByTestId("sign-page-header")).toHaveTextContent(Titles[key]); expect(screen.getByTestId("app-name")).toHaveTextContent("mockDappName"); + if (Hints[key].header && Hints[key].description) { + expect(screen.getByTestId("hints-accordion")).toHaveTextContent(Hints[key].header); + expect(screen.getByTestId("hints-accordion")).toHaveTextContent(Hints[key].description); + } else { + expect(screen.queryByTestId("hints-accordion")).not.toBeInTheDocument(); + } const signButton = screen.getByRole("button", { name: "Confirm Transaction", diff --git a/apps/web/src/components/SendFlow/common/StakeSignPage.tsx b/apps/web/src/components/SendFlow/common/StakeSignPage.tsx index 60dfcdc508..436abc8e7d 100644 --- a/apps/web/src/components/SendFlow/common/StakeSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/StakeSignPage.tsx @@ -1,11 +1,11 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Stake } from "@umami/core"; +import { type Stake, Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { TezTile } from "../../AssetTiles/TezTile"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -27,6 +27,7 @@ export const StakeSignPage = ({
+ diff --git a/apps/web/src/components/SendFlow/common/TezSignPage.tsx b/apps/web/src/components/SendFlow/common/TezSignPage.tsx index 85f1ba853f..8fad707f6f 100644 --- a/apps/web/src/components/SendFlow/common/TezSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/TezSignPage.tsx @@ -1,12 +1,12 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type TezTransfer } from "@umami/core"; +import { type TezTransfer, Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; import { TezTile } from "../../AssetTiles/TezTile"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -27,6 +27,7 @@ export const TezSignPage = ({
+ diff --git a/apps/web/src/components/SendFlow/common/UndelegationSignPage.tsx b/apps/web/src/components/SendFlow/common/UndelegationSignPage.tsx index edd67f48f2..4bca5203c9 100644 --- a/apps/web/src/components/SendFlow/common/UndelegationSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/UndelegationSignPage.tsx @@ -1,10 +1,11 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; +import { Titles } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { AdvancedSettingsAccordion } from "../../AdvancedSettingsAccordion"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -24,6 +25,7 @@ export const UndelegationSignPage = ({
+ From diff --git a/apps/web/src/components/SendFlow/common/UnstakeSignPage.tsx b/apps/web/src/components/SendFlow/common/UnstakeSignPage.tsx index 28eb56c198..fb69a67c3b 100644 --- a/apps/web/src/components/SendFlow/common/UnstakeSignPage.tsx +++ b/apps/web/src/components/SendFlow/common/UnstakeSignPage.tsx @@ -1,11 +1,11 @@ import { Flex, FormLabel, ModalBody, ModalContent, ModalFooter } from "@chakra-ui/react"; -import { type Unstake } from "@umami/core"; +import { Titles, type Unstake } from "@umami/core"; import { FormProvider, useForm } from "react-hook-form"; import { Header } from "./Header"; import { AddressTile } from "../../AddressTile/AddressTile"; import { TezTile } from "../../AssetTiles/TezTile"; -import { Titles } from "../../Titles/Titles"; +import { HintsAccordion } from "../../HintsAccordion"; import { SignButton } from "../SignButton"; import { SignPageFee } from "../SignPageFee"; import { type CalculatedSignProps, type SdkSignPageProps } from "../utils"; @@ -27,6 +27,7 @@ export const UnstakeSignPage = ({
+ diff --git a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx index 563f36c721..81705fdf93 100644 --- a/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx +++ b/apps/web/src/components/beacon/useHandleBeaconMessage.test.tsx @@ -407,6 +407,7 @@ describe("", () => { ); expect(screen.getByTestId("sign-page-header")).toHaveTextContent("Send Request"); expect(screen.getByTestId("app-name")).toHaveTextContent("mockDappName"); + expect(screen.queryByTestId("hints-accordion")).not.toBeInTheDocument(); expect(mockToast).not.toHaveBeenCalled(); }); diff --git a/packages/core/src/Hints.ts b/packages/core/src/Hints.ts new file mode 100644 index 0000000000..119262b774 --- /dev/null +++ b/packages/core/src/Hints.ts @@ -0,0 +1,48 @@ +import { type SignPage } from "./Titles"; + +type HintData = { + header?: string; + description?: string; +}; +const basicOperationHeader = undefined; +const basicOperationDescription = undefined; + +const finalizationConsequences = + "Finalized funds are returned to your balance, allowing you to spend them or stake them again to earn rewards. Delegation rewards for these funds stop once they are finalized."; +const unstakingConsequences = `The unstaking process takes 4 cycles (~10 days) to unlock your staked funds, making them finalizable on request. You will receive delegation rewards for finalizable funds. ${finalizationConsequences}`; + +export const Hints: Record = { + TezSignPage: { + header: basicOperationHeader, + description: basicOperationDescription, + }, + OriginationOperationSignPage: { + header: basicOperationHeader, + description: basicOperationDescription, + }, + ContractCallSignPage: { + header: basicOperationHeader, + description: basicOperationDescription, + }, + DelegationSignPage: { + header: "It takes 2 cycles (~6 days) to start receiving delegation rewards.", + description: + "Bakers typically distribute delegation rewards every cycle (~3 days). Ensure the delegation fee offered by the baker is less than 100%; otherwise, you may not receive rewards. Delegation means that all your funds are delegated to one baker. The funds remain spendable, and you can cancel or change the delegation at any time.", + }, + UndelegationSignPage: { + header: "Stops both delegation and staking. Restoring rewards takes 2 cycles (~6 days).", + description: `Undelegation takes effect immediately. However, you will still receive delegation rewards for the current and the next 2 cycles, as baking rights are computed 2 cycles in advance. By undelegating, you also initiate the unstaking process if you have staked. ${unstakingConsequences}`, + }, + StakeSignPage: { + header: "You will start receiving rewards within 1 cycle (~3 days).", + description: `Your spendable balance will be reduced by the amount staked. You will start accruing staking rewards within 1 cycle (~3 days). Staking rewards are compounded on your staked balance. You can cancel staking any time. ${unstakingConsequences}`, + }, + UnstakeSignPage: { + header: "Takes at least 4 cycles (~10 days) to complete.", + description: unstakingConsequences, + }, + FinalizeUnstakeSignPage: { + header: "Takes 1 block (8 seconds) to complete.", + description: `Finalizing applies to all finalizable funds in your balance. ${finalizationConsequences}`, + }, +}; diff --git a/apps/web/src/components/Titles/Titles.tsx b/packages/core/src/Titles.tsx similarity index 90% rename from apps/web/src/components/Titles/Titles.tsx rename to packages/core/src/Titles.tsx index 9dce1535e3..3afc1c775b 100644 --- a/apps/web/src/components/Titles/Titles.tsx +++ b/packages/core/src/Titles.tsx @@ -1,3 +1,4 @@ +export type SignPage = keyof typeof Titles; export const Titles: Record = { TezSignPage: "Send Request", ContractCallSignPage: "Contract Call Request", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 86c65c256f..cfe48ba635 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -6,8 +6,10 @@ export * from "./Delegate"; export * from "./estimate"; export * from "./execute"; export * from "./helpers"; +export * from "./Hints"; export * from "./Operation"; export * from "./testUtils"; +export * from "./Titles"; export * from "./Token"; export * from "./TokenBalance"; export * from "./beaconUtils";