diff --git a/apps/web/src/components/HintsAccordion/HintsAccordion.tsx b/apps/web/src/components/HintsAccordion/HintsAccordion.tsx new file mode 100644 index 000000000..9685034a2 --- /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 000000000..b8dc309c6 --- /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 fbfee6d10..f462b0419 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 83ff847bb..d60bdd55d 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 d37cdbc66..bf525d714 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 2a01e2b0c..ed68d3ec1 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 ac67f18ec..20712fd0f 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 945edb8ae..9998c30be 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 60dfcdc50..436abc8e7 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 85f1ba853..8fad707f6 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 edd67f48f..4bca5203c 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 28eb56c19..fb69a67c3 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 563f36c72..81705fdf9 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 000000000..119262b77 --- /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 9dce1535e..3afc1c775 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 86c65c256..cfe48ba63 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";