From d4ea05b42fd47c04cada8d424e7b39e2d05aff9f Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 11:58:54 +0200 Subject: [PATCH 01/37] Removal of unnecessary code from deposit flow --- .../TransactionModal/ActionFormModal.tsx | 68 ++++++++----------- .../ActiveStakingStep/OverviewModal/index.tsx | 36 ---------- .../ActiveStakingStep/OverviewModal/steps.tsx | 25 ------- .../ActiveStakingStep/SignMessageModal.tsx | 58 ---------------- .../ActiveStakingStep/index.tsx | 6 -- .../ActiveUnstakingStep/SignMessageModal.tsx | 8 +-- .../TransactionModal/ModalContentWrapper.tsx | 12 +--- .../TransactionModal/ResumeModal.tsx | 50 -------------- .../TransactionModal/SuccessModal.tsx | 1 - .../src/components/TransactionModal/index.tsx | 45 +++--------- .../shared/alerts/ReceiveSTBTCAlert.tsx | 18 ----- dapp/src/components/shared/alerts/index.ts | 1 - dapp/src/contexts/ModalFlowContext.tsx | 2 - dapp/src/hooks/useInitApp.ts | 4 +- .../pages/OverviewPage/PositionDetails.tsx | 10 ++- dapp/src/types/action-flow.ts | 6 +- 16 files changed, 51 insertions(+), 299 deletions(-) delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx delete mode 100644 dapp/src/components/TransactionModal/ResumeModal.tsx delete mode 100644 dapp/src/components/shared/alerts/ReceiveSTBTCAlert.tsx diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 0f7c824c9..e536d426e 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,15 +1,6 @@ import React, { useCallback, useState } from "react" +import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" import { - ModalBody, - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, - ModalCloseButton, -} from "@chakra-ui/react" -import { - useModalFlowContext, useStakeFlowContext, useTransactionContext, useWalletContext, @@ -20,16 +11,36 @@ import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" -const TABS = Object.values(ACTION_FLOW_TYPES) +const FORM_DATA: Record< + ActionFlowType, + { + header: string + FormComponent: ( + props: React.ComponentProps< + typeof StakeFormModal | typeof UnstakeFormModal + >, + ) => React.ReactNode + } +> = { + stake: { + header: "Deposit", + FormComponent: StakeFormModal, + }, + unstake: { + header: "Withdraw", + FormComponent: UnstakeFormModal, + }, +} -function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { +function ActionFormModal({ type }: { type: ActionFlowType }) { const { btcAccount, ethAccount } = useWalletContext() - const { type, setType } = useModalFlowContext() const { setTokenAmount } = useTransactionContext() const { initStake } = useStakeFlowContext() const [isLoading, setIsLoading] = useState(false) + const { header, FormComponent } = FORM_DATA[type] + const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address const ethAddress = ethAccount?.address @@ -67,34 +78,11 @@ function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { return ( <> {!isLoading && } + {header} - - - {TABS.map((actionFlowType) => ( - setType(actionFlowType)} - isDisabled={actionFlowType !== type && isLoading} - > - {actionFlowType} - - ))} - - - - - - - - - - + + + ) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx deleted file mode 100644 index c5806b663..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react" -import { - Button, - ModalBody, - ModalFooter, - ModalHeader, - StepNumber, -} from "@chakra-ui/react" -import StepperBase from "#/components/shared/StepperBase" -import { useModalFlowContext } from "#/hooks" -import { STEPS } from "./steps" - -export default function OverviewModal() { - const { goNext } = useModalFlowContext() - - return ( - <> - Staking steps overview - - } - steps={STEPS} - /> - - - - - - ) -} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx deleted file mode 100644 index 899fc7960..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react" -import { StepBase } from "#/components/shared/StepperBase" -import { Description, Title } from "../StakingStepsModalContent" - -export const STEPS: StepBase[] = [ - { - id: "sign-message", - title: Sign message, - description: ( - - You will sign a gas-free Ethereum message to indicate the address where - you'd like to get your stBTC liquid staking token. - - ), - }, - { - id: "deposit-btc", - title: Deposit BTC, - description: ( - - You will make a Bitcoin transaction to deposit and stake your BTC. - - ), - }, -] diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx deleted file mode 100644 index 37bba6883..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useCallback, useEffect, useState } from "react" -import { - useExecuteFunction, - useModalFlowContext, - useStakeFlowContext, - useToast, -} from "#/hooks" -import { logPromiseFailure } from "#/utils" -import { PROCESS_STATUSES, TOASTS, TOAST_IDS } from "#/types" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" -import StakingStepsModalContent from "./StakingStepsModalContent" - -const TOAST_ID = TOAST_IDS.SIGNING_ERROR -const TOAST = TOASTS[TOAST_ID] - -export default function SignMessageModal() { - const { goNext, setStatus } = useModalFlowContext() - const { signMessage } = useStakeFlowContext() - const [buttonText, setButtonText] = useState("Sign now") - const { closeToast, openToast } = useToast() - - useEffect(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, [setStatus]) - - const onSignMessageSuccess = useCallback(() => { - closeToast(TOAST_ID) - goNext() - }, [closeToast, goNext]) - - const onSignMessageError = useCallback(() => { - openToast({ - id: TOAST_ID, - render: TOAST, - }) - setButtonText("Try again") - }, [openToast]) - - const handleSignMessage = useExecuteFunction( - signMessage, - onSignMessageSuccess, - onSignMessageError, - ) - - const handleSignMessageWrapper = useCallback(() => { - logPromiseFailure(handleSignMessage()) - }, [handleSignMessage]) - - return ( - - - - ) -} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx index 6776365f4..d03576d31 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx @@ -1,17 +1,11 @@ import React from "react" import { ACTION_FLOW_STEPS_TYPES, ACTION_FLOW_TYPES } from "#/types" -import SignMessageModal from "./SignMessageModal" import DepositBTCModal from "./DepositBTCModal" -import OverviewModal from "./OverviewModal" const STEPS = ACTION_FLOW_STEPS_TYPES[ACTION_FLOW_TYPES.STAKE] export function ActiveStakingStep({ activeStep }: { activeStep: number }) { switch (activeStep) { - case STEPS.OVERVIEW: - return - case STEPS.SIGN_MESSAGE: - return case STEPS.DEPOSIT_BTC: return default: { diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index d462cab94..e46359396 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,18 +1,13 @@ -import React, { useCallback, useEffect } from "react" +import React, { useCallback } from "react" import { useExecuteFunction, useModalFlowContext } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" export default function SignMessageModal() { const { setStatus } = useModalFlowContext() - useEffect(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, [setStatus]) - const onSignMessageSuccess = useCallback(() => { setStatus(PROCESS_STATUSES.SUCCEEDED) }, [setStatus]) @@ -46,7 +41,6 @@ export default function SignMessageModal() { You will sign a gas-free Ethereum message to indicate the address where you'd like to get your stBTC liquid staking token. - - - - - ) -} diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index e6dac590e..5ea882804 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -46,7 +46,6 @@ export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { /> - + {/* TODO: Simplify the logic of opening modals */} + diff --git a/dapp/src/types/action-flow.ts b/dapp/src/types/action-flow.ts index ac31a0ff0..a587b58b9 100644 --- a/dapp/src/types/action-flow.ts +++ b/dapp/src/types/action-flow.ts @@ -7,9 +7,7 @@ export type ActionFlowType = (typeof ACTION_FLOW_TYPES)[keyof typeof ACTION_FLOW_TYPES] const STAKING_STEPS = { - OVERVIEW: 1, - SIGN_MESSAGE: 2, - DEPOSIT_BTC: 3, + DEPOSIT_BTC: 1, } as const const UNSTAKING_STEPS = { SIGN_MESSAGE: 1 } as const @@ -21,8 +19,6 @@ export const ACTION_FLOW_STEPS_TYPES = { export const PROCESS_STATUSES = { IDLE: "IDLE", - PAUSED: "PAUSED", - PENDING: "PENDING", LOADING: "LOADING", FAILED: "FAILED", SUCCEEDED: "SUCCEEDED", From 148117f9d3c4c742dedb002de5d32df16c5134dd Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:04:36 +0200 Subject: [PATCH 02/37] Update `DepositBTCModal` component --- .../ActiveStakingStep/DepositBTCModal.tsx | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 4b68afa54..17c1dfe2d 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React, { useCallback } from "react" import { useDepositBTCTransaction, useDepositTelemetry, @@ -9,13 +9,16 @@ import { useTransactionContext, useWalletContext, } from "#/hooks" -import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" import { PROCESS_STATUSES } from "#/types" -import { CardAlert } from "#/components/shared/alerts" import { TOASTS, TOAST_IDS } from "#/types/toast" -import StakingStepsModalContent from "./StakingStepsModalContent" +import { ModalBody, ModalHeader, Highlight, useTimeout } from "@chakra-ui/react" +import Spinner from "#/components/shared/Spinner" +import { TextMd } from "#/components/shared/Typography" +import { CardAlert } from "#/components/shared/alerts" +import { ONE_SEC_IN_MILLISECONDS } from "#/constants" +const DELAY = ONE_SEC_IN_MILLISECONDS * 3 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] @@ -27,9 +30,6 @@ export default function DepositBTCModal() { const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() - const [isLoading, setIsLoading] = useState(false) - const [buttonText, setButtonText] = useState("Deposit BTC") - const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), [setStatus], @@ -57,7 +57,6 @@ export default function DepositBTCModal() { id: TOAST_ID, render: TOAST, }) - setButtonText("Try again") }, [openToast]) const onDepositBTCError = useCallback(() => showError(), [showError]) @@ -71,13 +70,11 @@ export default function DepositBTCModal() { if (!tokenAmount?.amount || !btcAddress || !depositReceipt || !ethAccount) return - setIsLoading(true) const response = await depositTelemetry( depositReceipt, btcAddress, ethAccount.address, ) - setIsLoading(false) if (response.verificationStatus === "valid") { logPromiseFailure(sendBitcoinTransaction(tokenAmount?.amount, btcAddress)) @@ -98,18 +95,23 @@ export default function DepositBTCModal() { logPromiseFailure(handledDepositBTC()) }, [handledDepositBTC]) + useTimeout(handledDepositBTCWrapper, DELAY) + return ( - - - - Make a Bitcoin transaction to deposit and stake your BTC. - - - + <> + Waiting transaction... + + + Please complete the transaction in your wallet. + + + + You will receive your Rewards once the deposit transaction is + completed. + + + + + ) } From a51979f05bb2bc6dd95c7d4534d3093dc7982fd8 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:09:45 +0200 Subject: [PATCH 03/37] Update `Sidebar` component --- dapp/src/assets/icons/ShieldPlus.tsx | 17 ---- dapp/src/assets/icons/index.ts | 1 - dapp/src/assets/images/right-sidebar-bg.png | Bin 124416 -> 0 bytes dapp/src/components/Sidebar.tsx | 70 ++++++++++++++++ dapp/src/components/Sidebar/index.tsx | 84 -------------------- 5 files changed, 70 insertions(+), 102 deletions(-) delete mode 100644 dapp/src/assets/icons/ShieldPlus.tsx delete mode 100644 dapp/src/assets/images/right-sidebar-bg.png create mode 100644 dapp/src/components/Sidebar.tsx delete mode 100644 dapp/src/components/Sidebar/index.tsx diff --git a/dapp/src/assets/icons/ShieldPlus.tsx b/dapp/src/assets/icons/ShieldPlus.tsx deleted file mode 100644 index b3fbea2bc..000000000 --- a/dapp/src/assets/icons/ShieldPlus.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react" -import { createIcon } from "@chakra-ui/react" - -export const ShieldPlusIcon = createIcon({ - displayName: "ShieldPlusIcon", - viewBox: "0 0 20 20", - path: ( - - ), -}) diff --git a/dapp/src/assets/icons/index.ts b/dapp/src/assets/icons/index.ts index 62d802cf8..e2832df45 100644 --- a/dapp/src/assets/icons/index.ts +++ b/dapp/src/assets/icons/index.ts @@ -5,7 +5,6 @@ export * from "./ArrowRight" export * from "./AcreLogo" export * from "./stBTC" export * from "./BTC" -export * from "./ShieldPlus" export * from "./Pending" export * from "./Syncing" export * from "./Complete" diff --git a/dapp/src/assets/images/right-sidebar-bg.png b/dapp/src/assets/images/right-sidebar-bg.png deleted file mode 100644 index 540134e584502ef1baf0217d3b4d02648cd5be32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124416 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�ec2i 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

R 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 Z|3B(bFvffUMuGqU002ovPDHLkV1j6o9i;#O diff --git a/dapp/src/components/Sidebar.tsx b/dapp/src/components/Sidebar.tsx new file mode 100644 index 000000000..2b154650d --- /dev/null +++ b/dapp/src/components/Sidebar.tsx @@ -0,0 +1,70 @@ +import React from "react" +import { + Box, + Card, + CardBody, + Flex, + useMultiStyleConfig, +} from "@chakra-ui/react" +import { useSidebar, useDocsDrawer } from "#/hooks" +import { TextSm } from "./shared/Typography" +import ButtonLink from "./shared/ButtonLink" + +const BUTTONS = [ + { label: "Docs", variant: "solid" }, + { label: "FAQ", colorScheme: "gold" }, + { label: "Token Contract", colorScheme: "gold" }, + { label: "Bridge Contract", colorScheme: "gold" }, +] + +const BENEFITS = [ + { label: "1x Rewards Boost", icon: undefined }, + { label: "1x Mystery Box", icon: undefined }, + { label: "1x Season Key", icon: undefined }, +] + +export default function Sidebar() { + const { isOpen } = useSidebar() + const { onOpen: openDocsDrawer } = useDocsDrawer() + const styles = useMultiStyleConfig("Sidebar") + + return ( + + + Rewards you’ll unlock + + + {BENEFITS.map(({ label }) => ( + + + {label} + {/* TODO: Add a correct icon */} + + + ))} + + + {BUTTONS.map(({ label, variant, colorScheme }) => ( + + {label} + + ))} + + + ) +} diff --git a/dapp/src/components/Sidebar/index.tsx b/dapp/src/components/Sidebar/index.tsx deleted file mode 100644 index 0b485b142..000000000 --- a/dapp/src/components/Sidebar/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react" -import { - Box, - Icon, - useMultiStyleConfig, - Image, - Card, - CardBody, - CardHeader, - CardFooter, - HStack, - Link, -} from "@chakra-ui/react" -import RightSidebar from "#/assets/images/right-sidebar-bg.png" -import { useSidebar, useDocsDrawer } from "#/hooks" -import { ShieldPlusIcon } from "#/assets/icons" -import { TextMd, TextSm } from "../shared/Typography" -import ButtonLink from "../shared/ButtonLink" - -const readMoreEarnings = "https://#" - -const BUTTONS = [ - { label: "FAQ" }, - { label: "Token Contract" }, - { label: "Bridge Contract" }, -] - -export default function Sidebar() { - const { isOpen } = useSidebar() - const { onOpen: openDocsDrawer } = useDocsDrawer() - const styles = useMultiStyleConfig("Sidebar") - - return ( - - - - Docs - - - - - - - - - Maximize your earnings by using tBTC to deposit and redeem BTC in - DeFi! - - - - - - Read more - - - - - - - How we calculate fees - - - - - - Fees is software empowered by the Threshold DAO. - - - - - {BUTTONS.map(({ label }) => ( - - {label} - - ))} - - - ) -} From 4d9e3db04f89db28fbd283e1a40678e0699a3b1a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:35:40 +0200 Subject: [PATCH 04/37] Update global styles for modal component --- .../ActiveStakingStep/StakeFormModal/index.tsx | 2 +- .../ActiveUnstakingStep/UnstakeFormModal/index.tsx | 2 +- .../TransactionModal/MissingAccountModal.tsx | 4 ++-- dapp/src/theme/Modal.ts | 12 +++++++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 7e627e73d..4e7d0d443 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -27,7 +27,7 @@ function StakeFormModal({ minTokenAmount={minDepositAmount} maxTokenAmount={tokenBalance} /> - Stake + Stake ) } diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx index 981a43b62..7cdbaad15 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx @@ -47,7 +47,7 @@ function UnstakeFormModal({ - Unstake + Unstake ) } diff --git a/dapp/src/components/TransactionModal/MissingAccountModal.tsx b/dapp/src/components/TransactionModal/MissingAccountModal.tsx index d93f7287e..1215371a3 100644 --- a/dapp/src/components/TransactionModal/MissingAccountModal.tsx +++ b/dapp/src/components/TransactionModal/MissingAccountModal.tsx @@ -36,7 +36,7 @@ export default function MissingAccountModal({ <> {name} account not installed - + @@ -53,7 +53,7 @@ export default function MissingAccountModal({ - + diff --git a/dapp/src/theme/Modal.ts b/dapp/src/theme/Modal.ts index 8436cc293..33a6ac658 100644 --- a/dapp/src/theme/Modal.ts +++ b/dapp/src/theme/Modal.ts @@ -2,7 +2,6 @@ import { modalAnatomy as parts } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react" const baseStyleDialog = defineStyle({ - p: 4, borderWidth: "var(--chakra-space-modal_borderWidth)", boxShadow: "none", borderColor: "white", @@ -31,11 +30,13 @@ const baseStyleOverlay = defineStyle({ }) const baseStyleHeader = defineStyle({ - textAlign: "center", + textAlign: "left", fontSize: "lg", lineHeight: "lg", fontWeight: "bold", - py: 6, + pt: 10, + px: 10, + pb: 8, }) const baseStyleBody = defineStyle({ @@ -45,11 +46,16 @@ const baseStyleBody = defineStyle({ flexDirection: "column", alignItems: "center", gap: 6, + pt: 0, + px: 8, + pb: 10, }) const baseStyleFooter = defineStyle({ flexDirection: "column", gap: 6, + px: 8, + pb: 10, }) const multiStyleConfig = createMultiStyleConfigHelpers(parts.keys) From 69e0169d6bcd87d9bd9924f1bdff0a03fd058dfe Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 13:25:44 +0200 Subject: [PATCH 05/37] Update a stake details in form modal --- .../ActiveStakingStep/StakeFormModal/StakeDetails.tsx | 1 - dapp/src/components/shared/FeesDetails/FeesItem.tsx | 10 ++-------- dapp/src/components/shared/FeesDetails/index.tsx | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx index 6fc73f611..416d691b0 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx @@ -50,7 +50,6 @@ function StakeDetails({ /> & - Pick + Pick function FeesDetailsAmountItem({ label, - sublabel, tooltip, from, to, }: FeesDetailsItemAmountItemProps) { return ( - + Date: Wed, 8 May 2024 13:26:35 +0200 Subject: [PATCH 06/37] Show currency conversion in form --- .../StakeFormModal/index.tsx | 1 + .../TokenAmountForm/TokenAmountFormBase.tsx | 3 ++ .../shared/TokenBalanceInput/index.tsx | 28 +++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 4e7d0d443..24c590b9d 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -18,6 +18,7 @@ function StakeFormModal({ {children} diff --git a/dapp/src/components/shared/TokenBalanceInput/index.tsx b/dapp/src/components/shared/TokenBalanceInput/index.tsx index 2f3a89ff8..3c198d369 100644 --- a/dapp/src/components/shared/TokenBalanceInput/index.tsx +++ b/dapp/src/components/shared/TokenBalanceInput/index.tsx @@ -19,6 +19,7 @@ import { } from "#/utils" import { CurrencyType } from "#/types" import { IconInfoCircle } from "@tabler/icons-react" +import { useCurrencyConversion } from "#/hooks" import NumberFormatInput, { NumberFormatInputValues, } from "../NumberFormatInput" @@ -58,18 +59,25 @@ function HelperErrorText({ } type FiatCurrencyBalanceProps = { - fiatAmount?: string - fiatCurrency?: CurrencyType + amount: bigint + currency: CurrencyType + fiatCurrency: CurrencyType } function FiatCurrencyBalance({ - fiatAmount, + amount, + currency, fiatCurrency, }: FiatCurrencyBalanceProps) { const styles = useMultiStyleConfig("Form") const { fontWeight } = styles.helperText - if (fiatAmount && fiatCurrency) { + const fiatAmount = useCurrencyConversion({ + from: { amount, currency }, + to: { currency: fiatCurrency }, + }) + + if (fiatAmount !== undefined) { return ( void } & InputProps & - HelperErrorTextProps & - FiatCurrencyBalanceProps + HelperErrorTextProps export default function TokenBalanceInput({ amount, @@ -105,7 +113,6 @@ export default function TokenBalanceInput({ errorMsgText, helperText, hasError = false, - fiatAmount, fiatCurrency, ...inputProps }: TokenBalanceInputProps) { @@ -118,6 +125,8 @@ export default function TokenBalanceInput({ valueRef.current = value ? userAmountToBigInt(value, decimals) : undefined } + const showConversionBalance = amount !== undefined && !!fiatCurrency + return ( @@ -163,10 +172,11 @@ export default function TokenBalanceInput({ errorMsgText={errorMsgText} hasError={hasError} /> - {!hasError && !helperText && ( + {!hasError && !helperText && showConversionBalance && ( From 9dad5b3b5c062afffa1940a88ac68f7a384fe8b7 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 13:51:30 +0200 Subject: [PATCH 07/37] Update `SuccessModal` component --- .../TransactionModal/SuccessModal.tsx | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 5ea882804..2c2a2c5de 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -2,6 +2,7 @@ import React from "react" import { Box, Button, + HStack, ModalBody, ModalFooter, ModalHeader, @@ -11,11 +12,53 @@ import { LoadingSpinnerSuccessIcon } from "#/assets/icons" import { useModalFlowContext } from "#/hooks" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { ACTION_FLOW_TYPES, ActionFlowType, TokenAmount } from "#/types" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" +import { TextMd } from "../shared/Typography" +import Spinner from "../shared/Spinner" +import BlockExplorerLink from "../shared/BlockExplorerLink" -const HEADER = { - [ACTION_FLOW_TYPES.STAKE]: "Staking successful!", - [ACTION_FLOW_TYPES.UNSTAKE]: "Unstaking successful!", +const CONTENT: Record< + ActionFlowType, + { + header: string + renderBody: (tokenAmount: TokenAmount) => React.ReactNode + footer: string + } +> = { + [ACTION_FLOW_TYPES.STAKE]: { + header: "Deposit received", + renderBody: (tokenAmount) => ( + <> + + + + {/* TODO: Use correct tx hash and update styles */} + + + ), + footer: "The staking will continue in the background", + }, + [ACTION_FLOW_TYPES.UNSTAKE]: { + header: "Withdrawal initiated", + renderBody: () => ( + + You’ll receive your funds once the unstaking process is completed. + Follow the progress in your dashboard. + + ), + footer: "The unstaking will continue in the background", + }, } type SuccessModalProps = { @@ -26,31 +69,25 @@ type SuccessModalProps = { export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { const { onClose } = useModalFlowContext() + const { header, footer, renderBody } = CONTENT[type] + return ( <> - {HEADER[type]} + {header} - - - + {renderBody(tokenAmount)} - + + + + {footer} + ) From 10729f8b3155254ff786d15df79d32be7db4a497 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 14:09:01 +0200 Subject: [PATCH 08/37] Update styles for error modals --- .../ActiveStakingStep/StakingErrorModal/RetryModal.tsx | 8 +++++--- .../StakingErrorModal/ServerErrorModal.tsx | 7 ++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx index b1a33e03e..c1c144eae 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx @@ -41,8 +41,10 @@ export default function RetryModal({ retry }: { retry: () => void }) { return ( <> - Oops! There was an error. - + + Oops! There was an error. + + @@ -66,7 +68,7 @@ export default function RetryModal({ retry }: { retry: () => void }) { - + diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx index ff4cc6b5e..179c0d62c 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx @@ -32,10 +32,10 @@ export default function ServerErrorModal({ return ( <> - + We're currently facing system issues. - + @@ -56,7 +56,6 @@ export default function ServerErrorModal({ From 879ffcc77e125e49d7d69550872b79de4a986f30 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 09:11:03 +0200 Subject: [PATCH 09/37] Update value of delay for triggering Bitcoin transaction --- .../TransactionModal/ActiveStakingStep/DepositBTCModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 17c1dfe2d..1878b03c3 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -18,7 +18,7 @@ import { TextMd } from "#/components/shared/Typography" import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" -const DELAY = ONE_SEC_IN_MILLISECONDS * 3 +const DELAY = ONE_SEC_IN_MILLISECONDS * 2 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] From 0c49175839b96ef7e306676237861ede3b87fc34 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 10:47:54 +0200 Subject: [PATCH 10/37] Move data from context to redux for action flow --- .../TransactionModal/ActiveFlowStep.tsx | 11 ++++- .../ActiveStakingStep/DepositBTCModal.tsx | 9 ++-- .../StakingErrorModal/index.tsx | 10 ++--- .../ActiveUnstakingStep/SignMessageModal.tsx | 11 +++-- .../TransactionModal/ModalContentWrapper.tsx | 6 ++- .../src/components/TransactionModal/index.tsx | 30 ++++---------- dapp/src/contexts/ModalFlowContext.tsx | 6 --- dapp/src/hooks/store/index.ts | 3 ++ .../hooks/store/useActionFlowActiveStep.ts | 6 +++ dapp/src/hooks/store/useActionFlowStatus.ts | 6 +++ dapp/src/hooks/store/useActionFlowType.ts | 6 +++ .../store/action-flow/actionFlowSelectors.ts | 11 +++++ dapp/src/store/action-flow/actionFlowSlice.ts | 41 +++++++++++++++++++ dapp/src/store/action-flow/index.ts | 2 + dapp/src/store/reducer.ts | 2 + 15 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 dapp/src/hooks/store/useActionFlowActiveStep.ts create mode 100644 dapp/src/hooks/store/useActionFlowStatus.ts create mode 100644 dapp/src/hooks/store/useActionFlowType.ts create mode 100644 dapp/src/store/action-flow/actionFlowSelectors.ts create mode 100644 dapp/src/store/action-flow/actionFlowSlice.ts create mode 100644 dapp/src/store/action-flow/index.ts diff --git a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx index 0e27f2f74..5465363af 100644 --- a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx +++ b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx @@ -1,5 +1,9 @@ import React, { ReactElement, useEffect } from "react" -import { useModalFlowContext } from "#/hooks" +import { + useModalFlowContext, + useActionFlowActiveStep, + useActionFlowType, +} from "#/hooks" import { ACTION_FLOW_STEPS_TYPES, ActionFlowType, @@ -18,7 +22,10 @@ const FLOW: Record ReactElement> = { } export function ActiveFlowStep() { - const { activeStep, type, onClose } = useModalFlowContext() + const { onClose } = useModalFlowContext() + const activeStep = useActionFlowActiveStep() + const type = useActionFlowType() + const numberOfSteps = Object.keys(ACTION_FLOW_STEPS_TYPES[type]).length useEffect(() => { diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 1878b03c3..0940b2f63 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -3,7 +3,6 @@ import { useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, - useModalFlowContext, useStakeFlowContext, useToast, useTransactionContext, @@ -17,6 +16,7 @@ import Spinner from "#/components/shared/Spinner" import { TextMd } from "#/components/shared/Typography" import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" +import { setStatus } from "#/store/action-flow" const DELAY = ONE_SEC_IN_MILLISECONDS * 2 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR @@ -25,19 +25,18 @@ const TOAST = TOASTS[TOAST_ID] export default function DepositBTCModal() { const { ethAccount } = useWalletContext() const { tokenAmount } = useTransactionContext() - const { setStatus } = useModalFlowContext() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + [], ) const onStakeBTCError = useCallback(() => { setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + }, []) const handleStake = useExecuteFunction( stake, @@ -50,7 +49,7 @@ export default function DepositBTCModal() { setStatus(PROCESS_STATUSES.LOADING) logPromiseFailure(handleStake()) - }, [closeToast, setStatus, handleStake]) + }, [closeToast, handleStake]) const showError = useCallback(() => { openToast({ diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index 6ae4e1276..1473e50ef 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -1,17 +1,13 @@ import React, { useCallback, useState } from "react" -import { - useExecuteFunction, - useModalFlowContext, - useStakeFlowContext, -} from "#/hooks" +import { useExecuteFunction, useStakeFlowContext } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { logPromiseFailure } from "#/utils" +import { setStatus } from "#/store/action-flow" import ServerErrorModal from "./ServerErrorModal" import RetryModal from "./RetryModal" import LoadingModal from "../../LoadingModal" export default function StakingErrorModal() { - const { setStatus } = useModalFlowContext() const { stake } = useStakeFlowContext() const [isLoading, setIsLoading] = useState(false) @@ -19,7 +15,7 @@ export default function StakingErrorModal() { const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + [], ) const onStakeBTCError = useCallback(() => setIsServerError(true), []) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index e46359396..e05bc5bec 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,21 +1,20 @@ import React, { useCallback } from "react" -import { useExecuteFunction, useModalFlowContext } from "#/hooks" +import { useExecuteFunction } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" +import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { - const { setStatus } = useModalFlowContext() - const onSignMessageSuccess = useCallback(() => { setStatus(PROCESS_STATUSES.SUCCEEDED) - }, [setStatus]) + }, []) // TODO: After a failed attempt, we should display the message const onSignMessageError = useCallback(() => { setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + }, []) const handleSignMessage = useExecuteFunction( // TODO: Use a correct function from the SDK @@ -31,7 +30,7 @@ export default function SignMessageModal() { setTimeout(() => { logPromiseFailure(handleSignMessage()) }, 5000) - }, [setStatus, handleSignMessage]) + }, [handleSignMessage]) return ( <> diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 776b36ec3..914acf999 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,6 +1,7 @@ import React from "react" import { - useModalFlowContext, + useActionFlowStatus, + useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, useTransactionContext, @@ -23,7 +24,8 @@ export default function ModalContentWrapper({ const { btcAccount, ethAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() - const { type, status } = useModalFlowContext() + const status = useActionFlowStatus() + const type = useActionFlowType() const { tokenAmount } = useTransactionContext() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 545417ef3..803712f78 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react" +import React, { useEffect, useMemo } from "react" import { ModalFlowContext, ModalFlowContextValue, @@ -6,14 +6,13 @@ import { TransactionContextProvider, } from "#/contexts" import { useSidebar } from "#/hooks" -import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { ActionFlowType } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" +import { resetState, setType } from "#/store/action-flow" import ModalBase from "../shared/ModalBase" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" -const DEFAULT_ACTIVE_STEP = 1 - type TransactionModalProps = { type: ActionFlowType isOpen: boolean @@ -27,17 +26,9 @@ export default function TransactionModal({ }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() - const [activeStep, setActiveStep] = useState(DEFAULT_ACTIVE_STEP) - const [status, setStatus] = useState(PROCESS_STATUSES.IDLE) - - const handleGoNext = useCallback(() => { - setActiveStep((prevStep) => prevStep + 1) - }, []) - - const resetState = useCallback(() => { - setActiveStep(DEFAULT_ACTIVE_STEP) - setStatus(PROCESS_STATUSES.IDLE) - }, [setStatus]) + useEffect(() => { + setType(type) + }, [type]) useEffect(() => { let timeout: NodeJS.Timeout @@ -49,18 +40,13 @@ export default function TransactionModal({ timeout = setTimeout(resetState, 100) } return () => clearTimeout(timeout) - }, [isOpen, resetState, openSideBar, closeSidebar]) + }, [isOpen, openSideBar, closeSidebar]) const contextValue: ModalFlowContextValue = useMemo( () => ({ - type, - activeStep, - status, - setStatus, onClose, - goNext: handleGoNext, }), - [type, activeStep, status, onClose, handleGoNext], + [onClose], ) return ( diff --git a/dapp/src/contexts/ModalFlowContext.tsx b/dapp/src/contexts/ModalFlowContext.tsx index 87f3754c9..df0d9363f 100644 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ b/dapp/src/contexts/ModalFlowContext.tsx @@ -1,13 +1,7 @@ -import { ActionFlowType, ProcessStatus } from "#/types" import { createContext } from "react" export type ModalFlowContextValue = { - type: ActionFlowType - activeStep: number - status: ProcessStatus onClose: () => void - goNext: () => void - setStatus: React.Dispatch> } export const ModalFlowContext = createContext< diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index bdad282f8..9e114aada 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -3,3 +3,6 @@ export * from "./useAppSelector" export * from "./useEstimatedBTCBalance" export * from "./useSharesBalance" export * from "./useMinDepositAmount" +export * from "./useActionFlowType" +export * from "./useActionFlowStatus" +export * from "./useActionFlowActiveStep" diff --git a/dapp/src/hooks/store/useActionFlowActiveStep.ts b/dapp/src/hooks/store/useActionFlowActiveStep.ts new file mode 100644 index 000000000..f59e88bf4 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowActiveStep.ts @@ -0,0 +1,6 @@ +import { selectActionFlowActiveStep } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowActiveStep() { + return useAppSelector(selectActionFlowActiveStep) +} diff --git a/dapp/src/hooks/store/useActionFlowStatus.ts b/dapp/src/hooks/store/useActionFlowStatus.ts new file mode 100644 index 000000000..5fb18915d --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowStatus.ts @@ -0,0 +1,6 @@ +import { selectActionFlowStatus } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowStatus() { + return useAppSelector(selectActionFlowStatus) +} diff --git a/dapp/src/hooks/store/useActionFlowType.ts b/dapp/src/hooks/store/useActionFlowType.ts new file mode 100644 index 000000000..c2b0d3c65 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowType.ts @@ -0,0 +1,6 @@ +import { selectActionFlowType } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowType() { + return useAppSelector(selectActionFlowType) +} diff --git a/dapp/src/store/action-flow/actionFlowSelectors.ts b/dapp/src/store/action-flow/actionFlowSelectors.ts new file mode 100644 index 000000000..6de0a44bb --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSelectors.ts @@ -0,0 +1,11 @@ +import { ActionFlowType, ProcessStatus } from "#/types" +import { RootState } from ".." + +export const selectActionFlowType = (state: RootState): ActionFlowType => + state.actionFlow.type + +export const selectActionFlowActiveStep = (state: RootState): number => + state.actionFlow.activeStep + +export const selectActionFlowStatus = (state: RootState): ProcessStatus => + state.actionFlow.status diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts new file mode 100644 index 000000000..6a3b8a721 --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -0,0 +1,41 @@ +import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { PayloadAction, createSlice } from "@reduxjs/toolkit" + +type ActionFlowState = { + type: ActionFlowType + activeStep: number + status: ProcessStatus +} + +const initialState: ActionFlowState = { + type: "stake", + activeStep: 1, + status: PROCESS_STATUSES.IDLE, +} + +export const actionFlowSlice = createSlice({ + name: "action-flow", + initialState, + reducers: { + setType(state, action: PayloadAction) { + state.type = action.payload + }, + setActiveStep(state, action: PayloadAction) { + state.activeStep = action.payload + }, + setStatus(state, action: PayloadAction) { + state.status = action.payload + }, + goNextStep(state) { + state.activeStep += 1 + }, + resetState(state) { + state.type = initialState.type + state.activeStep = initialState.activeStep + state.status = initialState.status + }, + }, +}) + +export const { setType, setStatus, goNextStep, resetState } = + actionFlowSlice.actions diff --git a/dapp/src/store/action-flow/index.ts b/dapp/src/store/action-flow/index.ts new file mode 100644 index 000000000..261659e2f --- /dev/null +++ b/dapp/src/store/action-flow/index.ts @@ -0,0 +1,2 @@ +export * from "./actionFlowSlice" +export * from "./actionFlowSelectors" diff --git a/dapp/src/store/reducer.ts b/dapp/src/store/reducer.ts index a49f551e8..6ed0155e2 100644 --- a/dapp/src/store/reducer.ts +++ b/dapp/src/store/reducer.ts @@ -1,6 +1,8 @@ import { combineReducers } from "@reduxjs/toolkit" import { btcSlice } from "./btc/btcSlice" +import { actionFlowSlice } from "./action-flow/actionFlowSlice" export const reducer = combineReducers({ btc: btcSlice.reducer, + actionFlow: actionFlowSlice.reducer, }) From a38eb0fb3318ac99b3935317ea11c77cf49a76cc Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 12:53:14 +0200 Subject: [PATCH 11/37] Simplifying the logic for opening modals Modal is a global component and should be rendered on the top of the components tree. It shouldn't be rendered from the `PositionDetails` level. --- dapp/src/DApp.tsx | 2 + dapp/src/components/ModalRoot/index.tsx | 19 ++++++ .../components/ModalRoot/withBaseModal.tsx | 28 ++++++++ .../TransactionModal/ActiveFlowStep.tsx | 12 ++-- .../StakingErrorModal/ServerErrorModal.tsx | 2 +- .../TransactionModal/SuccessModal.tsx | 6 +- .../src/components/TransactionModal/index.tsx | 68 ++++++------------- .../src/components/shared/ModalBase/index.tsx | 13 ---- dapp/src/contexts/ModalFlowContext.tsx | 9 --- dapp/src/contexts/index.tsx | 1 - dapp/src/hooks/index.ts | 2 +- dapp/src/hooks/useModal.ts | 27 ++++++++ dapp/src/hooks/useModalFlowContext.ts | 14 ---- .../pages/OverviewPage/PositionDetails.tsx | 31 ++------- dapp/src/store/modal/index.ts | 2 + dapp/src/store/modal/modalSelectors.ts | 8 +++ dapp/src/store/modal/modalSlice.ts | 32 +++++++++ dapp/src/store/reducer.ts | 2 + dapp/src/types/index.ts | 1 + dapp/src/types/modal.ts | 12 ++++ 20 files changed, 169 insertions(+), 122 deletions(-) create mode 100644 dapp/src/components/ModalRoot/index.tsx create mode 100644 dapp/src/components/ModalRoot/withBaseModal.tsx delete mode 100644 dapp/src/components/shared/ModalBase/index.tsx delete mode 100644 dapp/src/contexts/ModalFlowContext.tsx create mode 100644 dapp/src/hooks/useModal.ts delete mode 100644 dapp/src/hooks/useModalFlowContext.ts create mode 100644 dapp/src/store/modal/index.ts create mode 100644 dapp/src/store/modal/modalSelectors.ts create mode 100644 dapp/src/store/modal/modalSlice.ts create mode 100644 dapp/src/types/modal.ts diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 71db1bc16..97e513488 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -17,6 +17,7 @@ import Sidebar from "./components/Sidebar" import DocsDrawer from "./components/DocsDrawer" import GlobalStyles from "./components/GlobalStyles" import { router } from "./router" +import ModalRoot from "./components/ModalRoot" function DApp() { useInitApp() @@ -29,6 +30,7 @@ function DApp() { + ) } diff --git a/dapp/src/components/ModalRoot/index.tsx b/dapp/src/components/ModalRoot/index.tsx new file mode 100644 index 000000000..22e6187f5 --- /dev/null +++ b/dapp/src/components/ModalRoot/index.tsx @@ -0,0 +1,19 @@ +import React, { ElementType } from "react" +import { useModal } from "#/hooks" +import { ModalType } from "#/types" +import TransactionModal from "../TransactionModal" + +const MODALS: Record = { + STAKE: TransactionModal, + UNSTAKE: TransactionModal, +} as const + +export default function ModalRoot() { + const { modalType, modalProps, closeModal } = useModal() + + if (!modalType) { + return null + } + const SpecificModal = MODALS[modalType] + return +} diff --git a/dapp/src/components/ModalRoot/withBaseModal.tsx b/dapp/src/components/ModalRoot/withBaseModal.tsx new file mode 100644 index 000000000..f0c24a99f --- /dev/null +++ b/dapp/src/components/ModalRoot/withBaseModal.tsx @@ -0,0 +1,28 @@ +import React, { ComponentType } from "react" +import { Modal, ModalContent, ModalOverlay } from "@chakra-ui/react" +import { BaseModalProps } from "#/types" + +export const MODAL_BASE_SIZE = "lg" + +function withBaseModal( + WrappedModalContent: ComponentType, +) { + return function ModalBase(props: T) { + const { closeModal } = props + return ( + + + + + + + ) + } +} + +export default withBaseModal diff --git a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx index 5465363af..bf8e5eafa 100644 --- a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx +++ b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx @@ -1,9 +1,5 @@ import React, { ReactElement, useEffect } from "react" -import { - useModalFlowContext, - useActionFlowActiveStep, - useActionFlowType, -} from "#/hooks" +import { useActionFlowActiveStep, useActionFlowType, useModal } from "#/hooks" import { ACTION_FLOW_STEPS_TYPES, ActionFlowType, @@ -22,7 +18,7 @@ const FLOW: Record ReactElement> = { } export function ActiveFlowStep() { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() const activeStep = useActionFlowActiveStep() const type = useActionFlowType() @@ -30,9 +26,9 @@ export function ActiveFlowStep() { useEffect(() => { if (activeStep > numberOfSteps) { - onClose() + closeModal() } - }, [activeStep, numberOfSteps, onClose]) + }, [activeStep, closeModal, numberOfSteps]) return FLOW[type](activeStep) } diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx index 179c0d62c..e83409338 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx @@ -15,7 +15,7 @@ import { CableWithPlugIcon, Info } from "#/assets/icons" import { TextMd } from "#/components/shared/Typography" import { EXTERNAL_HREF } from "#/constants" import IconWrapper from "#/components/shared/IconWrapper" -import { MODAL_BASE_SIZE } from "#/components/shared/ModalBase" +import { MODAL_BASE_SIZE } from "#/components/ModalRoot/withBaseModal" import { IconBrandDiscordFilled, IconReload, diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 2c2a2c5de..3e873612a 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -9,7 +9,7 @@ import { VStack, } from "@chakra-ui/react" import { LoadingSpinnerSuccessIcon } from "#/assets/icons" -import { useModalFlowContext } from "#/hooks" +import { useModal } from "#/hooks" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { ACTION_FLOW_TYPES, ActionFlowType, TokenAmount } from "#/types" import { TextMd } from "../shared/Typography" @@ -67,7 +67,7 @@ type SuccessModalProps = { } export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() const { header, footer, renderBody } = CONTENT[type] @@ -81,7 +81,7 @@ export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 803712f78..13f6ed82d 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,29 +1,18 @@ -import React, { useEffect, useMemo } from "react" -import { - ModalFlowContext, - ModalFlowContextValue, - StakeFlowProvider, - TransactionContextProvider, -} from "#/contexts" +import React, { useEffect } from "react" +import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" import { useSidebar } from "#/hooks" -import { ActionFlowType } from "#/types" +import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" -import { resetState, setType } from "#/store/action-flow" -import ModalBase from "../shared/ModalBase" +import { setType } from "#/store/action-flow" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" +import withBaseModal from "../ModalRoot/withBaseModal" type TransactionModalProps = { type: ActionFlowType - isOpen: boolean - onClose: () => void -} +} & BaseModalProps -export default function TransactionModal({ - type, - isOpen, - onClose, -}: TransactionModalProps) { +function TransactionModalBase({ type }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() useEffect(() => { @@ -31,36 +20,21 @@ export default function TransactionModal({ }, [type]) useEffect(() => { - let timeout: NodeJS.Timeout - - if (isOpen) { - openSideBar() - } else { - closeSidebar() - timeout = setTimeout(resetState, 100) - } - return () => clearTimeout(timeout) - }, [isOpen, openSideBar, closeSidebar]) - - const contextValue: ModalFlowContextValue = useMemo( - () => ({ - onClose, - }), - [onClose], - ) + openSideBar() + return () => closeSidebar() + }, [closeSidebar, openSideBar]) return ( - - - - - - - - - - - - + + + + + + + + ) } + +const TransactionModal = withBaseModal(TransactionModalBase) +export default TransactionModal diff --git a/dapp/src/components/shared/ModalBase/index.tsx b/dapp/src/components/shared/ModalBase/index.tsx deleted file mode 100644 index 416b22850..000000000 --- a/dapp/src/components/shared/ModalBase/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import { Modal, ModalContent, ModalOverlay, ModalProps } from "@chakra-ui/react" - -export const MODAL_BASE_SIZE = "lg" - -export default function ModalBase({ children, ...restProps }: ModalProps) { - return ( - - - {children} - - ) -} diff --git a/dapp/src/contexts/ModalFlowContext.tsx b/dapp/src/contexts/ModalFlowContext.tsx deleted file mode 100644 index df0d9363f..000000000 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext } from "react" - -export type ModalFlowContextValue = { - onClose: () => void -} - -export const ModalFlowContext = createContext< - ModalFlowContextValue | undefined ->(undefined) diff --git a/dapp/src/contexts/index.tsx b/dapp/src/contexts/index.tsx index 47a8cd708..a3365497c 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,6 +3,5 @@ export * from "./WalletApiReactTransportProvider" export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" -export * from "./ModalFlowContext" export * from "./TransactionContext" export * from "./StakeFlowContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 1c483932d..b5f482748 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -7,7 +7,6 @@ export * from "./useRequestEthereumAccount" export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" -export * from "./useModalFlowContext" export * from "./useTransactionContext" export * from "./useTransactionDetails" export * from "./useDepositBTCTransaction" @@ -24,3 +23,4 @@ export * from "./useCountdown" export * from "./useActivities" export * from "./useSize" export * from "./useTransactionFee" +export * from "./useModal" diff --git a/dapp/src/hooks/useModal.ts b/dapp/src/hooks/useModal.ts new file mode 100644 index 000000000..8d213e785 --- /dev/null +++ b/dapp/src/hooks/useModal.ts @@ -0,0 +1,27 @@ +import { + closeModal, + openModal, + selectModalProps, + selectModalType, +} from "#/store/modal" +import { ModalProps, ModalType } from "#/types" +import { useAppDispatch } from "./store/useAppDispatch" +import { useAppSelector } from "./store/useAppSelector" + +export function useModal() { + const modalType = useAppSelector(selectModalType) + const modalProps = useAppSelector(selectModalProps) + const dispatch = useAppDispatch() + + const handleOpenModal = (type: ModalType, props?: ModalProps) => + dispatch(openModal({ modalType: type, props })) + + const handleCloseModal = () => dispatch(closeModal()) + + return { + modalType, + modalProps, + openModal: handleOpenModal, + closeModal: handleCloseModal, + } +} diff --git a/dapp/src/hooks/useModalFlowContext.ts b/dapp/src/hooks/useModalFlowContext.ts deleted file mode 100644 index fda6eb681..000000000 --- a/dapp/src/hooks/useModalFlowContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from "react" -import { ModalFlowContext } from "#/contexts" - -export function useModalFlowContext() { - const context = useContext(ModalFlowContext) - - if (!context) { - throw new Error( - "ModalFlowContext used outside of ModalFlowContext component", - ) - } - - return context -} diff --git a/dapp/src/pages/OverviewPage/PositionDetails.tsx b/dapp/src/pages/OverviewPage/PositionDetails.tsx index 179f46c6d..8a432b9f1 100644 --- a/dapp/src/pages/OverviewPage/PositionDetails.tsx +++ b/dapp/src/pages/OverviewPage/PositionDetails.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React from "react" import { Button, CardBody, @@ -9,23 +9,15 @@ import { } from "@chakra-ui/react" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { TextMd } from "#/components/shared/Typography" -import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" -import TransactionModal from "#/components/TransactionModal" +import { MODAL_TYPES } from "#/types" import { useEstimatedBTCBalance } from "#/hooks/store" import { LiquidStakingTokenPopover } from "#/components/LiquidStakingTokenPopover" -import { useSize } from "#/hooks" +import { useModal, useSize } from "#/hooks" export default function PositionDetails(props: CardProps) { const estimatedBtcBalance = useEstimatedBTCBalance() const { ref, size } = useSize() - - const [actionFlowType, setActionFlowType] = useState< - ActionFlowType | undefined - >(undefined) - - const handleCloseTransactionModal = useCallback(() => { - setActionFlowType(undefined) - }, []) + const { openModal } = useModal() return ( @@ -50,29 +42,18 @@ export default function PositionDetails(props: CardProps) { - {/* TODO: Simplify the logic of opening modals */} - - ) } diff --git a/dapp/src/store/modal/index.ts b/dapp/src/store/modal/index.ts new file mode 100644 index 000000000..06ad11596 --- /dev/null +++ b/dapp/src/store/modal/index.ts @@ -0,0 +1,2 @@ +export * from "./modalSlice" +export * from "./modalSelectors" diff --git a/dapp/src/store/modal/modalSelectors.ts b/dapp/src/store/modal/modalSelectors.ts new file mode 100644 index 000000000..39f642e8e --- /dev/null +++ b/dapp/src/store/modal/modalSelectors.ts @@ -0,0 +1,8 @@ +import { ModalProps, ModalType } from "#/types" +import { RootState } from ".." + +export const selectModalType = (state: RootState): ModalType | null => + state.modal.modalType + +export const selectModalProps = (state: RootState): ModalProps | undefined => + state.modal.props diff --git a/dapp/src/store/modal/modalSlice.ts b/dapp/src/store/modal/modalSlice.ts new file mode 100644 index 000000000..9877bfbd9 --- /dev/null +++ b/dapp/src/store/modal/modalSlice.ts @@ -0,0 +1,32 @@ +import { ModalType, ModalProps } from "#/types" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" + +type ModalState = { + modalType: ModalType | null + props?: ModalProps +} + +const initialState: ModalState = { + modalType: null, + props: {}, +} + +export const modalSlice = createSlice({ + name: "modal", + initialState, + reducers: { + openModal: ( + state: ModalState, + action: PayloadAction<{ modalType: ModalType; props?: ModalProps }>, + ) => { + state.modalType = action.payload.modalType + state.props = action.payload.props + }, + closeModal: (state: ModalState) => { + state.modalType = null + state.props = {} + }, + }, +}) + +export const { openModal, closeModal } = modalSlice.actions diff --git a/dapp/src/store/reducer.ts b/dapp/src/store/reducer.ts index 6ed0155e2..1e15ef056 100644 --- a/dapp/src/store/reducer.ts +++ b/dapp/src/store/reducer.ts @@ -1,8 +1,10 @@ import { combineReducers } from "@reduxjs/toolkit" import { btcSlice } from "./btc/btcSlice" import { actionFlowSlice } from "./action-flow/actionFlowSlice" +import { modalSlice } from "./modal/modalSlice" export const reducer = combineReducers({ btc: btcSlice.reducer, actionFlow: actionFlowSlice.reducer, + modal: modalSlice.reducer, }) diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index b3b047b18..19adfd5ab 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -14,3 +14,4 @@ export * from "./time" export * from "./size" export * from "./toast" export * from "./fee" +export * from "./modal" diff --git a/dapp/src/types/modal.ts b/dapp/src/types/modal.ts new file mode 100644 index 000000000..75febc189 --- /dev/null +++ b/dapp/src/types/modal.ts @@ -0,0 +1,12 @@ +export type ModalProps = Record + +export type BaseModalProps = { + closeModal: () => void +} + +export const MODAL_TYPES = { + STAKE: "STAKE", + UNSTAKE: "UNSTAKE", +} as const + +export type ModalType = (typeof MODAL_TYPES)[keyof typeof MODAL_TYPES] From bd1e73a306132c5415ed92fff6ade558f2517806 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Thu, 9 May 2024 13:01:41 +0200 Subject: [PATCH 12/37] Implement `PageLayout` component --- .../OverviewPage/PageLayout/PageLayout.tsx | 51 +++++++++++++++++++ .../PageLayout/PageLayoutSidebar.tsx | 8 +++ .../pages/OverviewPage/PageLayout/index.ts | 2 + dapp/src/theme/index.ts | 3 ++ 4 files changed, 64 insertions(+) create mode 100644 dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx create mode 100644 dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx create mode 100644 dapp/src/pages/OverviewPage/PageLayout/index.ts diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx new file mode 100644 index 000000000..b1d996294 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { Grid, GridProps, Box } from "@chakra-ui/react" +import PageLayoutSidebar from "./PageLayoutSidebar" + +type PageLayoutProps = Omit & { + children: React.ReactNode + leftSidebar: React.ReactNode + rightSidebar: React.ReactNode +} + +function PageLayout(props: PageLayoutProps) { + const { children, leftSidebar, rightSidebar, ...restProps } = props + const isSidebarPropsInvalid = [leftSidebar, rightSidebar].some( + (value) => !React.isValidElement(value) || value.type !== PageLayoutSidebar, + ) + + if (isSidebarPropsInvalid) { + throw new Error("Sidebars must be wrapped with `PageLayout.Sidebar`.") + } + + return ( + + + {children} + + {leftSidebar} + {rightSidebar} + + ) +} + +export default PageLayout diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx new file mode 100644 index 000000000..6abb979e9 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx @@ -0,0 +1,8 @@ +import React from "react" +import { StackProps, VStack } from "@chakra-ui/react" + +function PageLayoutSidebar(props: StackProps) { + return +} + +export default PageLayoutSidebar diff --git a/dapp/src/pages/OverviewPage/PageLayout/index.ts b/dapp/src/pages/OverviewPage/PageLayout/index.ts new file mode 100644 index 000000000..4121f6e16 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/index.ts @@ -0,0 +1,2 @@ +export { default as PageLayout } from "./PageLayout" +export { default as PageLayoutSidebar } from "./PageLayoutSidebar" diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index add79614a..5cdff7e5a 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -45,6 +45,9 @@ const defaultTheme = { zIndices, semanticTokens, styles, + breakpoints: { + "2.5xl": "100.5rem", // 1608px + }, components: { Alert: alertTheme, Button: buttonTheme, From 126acdd02d0153b959b7b94650879371bf67bc0c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:15:53 +0200 Subject: [PATCH 13/37] Add the missing icons in the sidebar for benefit cards --- .../src/assets/images/rewards-boost-arrow.svg | 68 +++++++++++++++++++ dapp/src/components/Sidebar.tsx | 25 +++++-- 2 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 dapp/src/assets/images/rewards-boost-arrow.svg diff --git a/dapp/src/assets/images/rewards-boost-arrow.svg b/dapp/src/assets/images/rewards-boost-arrow.svg new file mode 100644 index 000000000..b8cb985cf --- /dev/null +++ b/dapp/src/assets/images/rewards-boost-arrow.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dapp/src/components/Sidebar.tsx b/dapp/src/components/Sidebar.tsx index 2b154650d..3dfc36915 100644 --- a/dapp/src/components/Sidebar.tsx +++ b/dapp/src/components/Sidebar.tsx @@ -5,10 +5,14 @@ import { CardBody, Flex, useMultiStyleConfig, + Image, } from "@chakra-ui/react" import { useSidebar, useDocsDrawer } from "#/hooks" -import { TextSm } from "./shared/Typography" +import rewardsBoostArrow from "#/assets/images/rewards-boost-arrow.svg" +import mysteryBoxIcon from "#/assets/images/mystery-box.svg" +import seasonKeyIcon from "#/assets/images/season-key.svg" import ButtonLink from "./shared/ButtonLink" +import { TextSm } from "./shared/Typography" const BUTTONS = [ { label: "Docs", variant: "solid" }, @@ -18,9 +22,9 @@ const BUTTONS = [ ] const BENEFITS = [ - { label: "1x Rewards Boost", icon: undefined }, - { label: "1x Mystery Box", icon: undefined }, - { label: "1x Season Key", icon: undefined }, + { label: "1x Rewards Boost", iconSrc: rewardsBoostArrow }, + { label: "1x Mystery Box", iconSrc: mysteryBoxIcon }, + { label: "1x Season Key", iconSrc: seasonKeyIcon }, ] export default function Sidebar() { @@ -39,16 +43,23 @@ export default function Sidebar() { Rewards you’ll unlock - {BENEFITS.map(({ label }) => ( + {BENEFITS.map(({ label, iconSrc }) => ( - + {label} - {/* TODO: Add a correct icon */} + ))} From 5a3b4c7a5bbcd532ff785d6463c38a38fc113ad4 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:25:11 +0200 Subject: [PATCH 14/37] Add ability to open `TransactionModal` from landing page --- .../pages/LandingPage/components/HeroSection.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dapp/src/pages/LandingPage/components/HeroSection.tsx b/dapp/src/pages/LandingPage/components/HeroSection.tsx index 14d8a7e4a..280f2a710 100644 --- a/dapp/src/pages/LandingPage/components/HeroSection.tsx +++ b/dapp/src/pages/LandingPage/components/HeroSection.tsx @@ -1,7 +1,11 @@ import React from "react" import { Button, Heading, VStack, Text } from "@chakra-ui/react" +import { useModal } from "#/hooks" +import { MODAL_TYPES } from "#/types" export default function HeroSection() { + const { openModal } = useModal() + return ( The open source, decentralized way to grow your bitcoin - From 1e64bc0abaf7a794e1332ec3738d4569555a2d08 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:27:05 +0200 Subject: [PATCH 15/37] Fix issue with closing modal --- dapp/src/components/ModalRoot/withBaseModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/dapp/src/components/ModalRoot/withBaseModal.tsx b/dapp/src/components/ModalRoot/withBaseModal.tsx index f0c24a99f..de804ca0d 100644 --- a/dapp/src/components/ModalRoot/withBaseModal.tsx +++ b/dapp/src/components/ModalRoot/withBaseModal.tsx @@ -14,6 +14,7 @@ function withBaseModal( isOpen onClose={closeModal} scrollBehavior="inside" + closeOnOverlayClick={false} size={MODAL_BASE_SIZE} > From b39b1200645c36b5fc26a126854b1056ba19a749 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 15:06:00 +0200 Subject: [PATCH 16/37] Fix issue with switching type for action flow --- dapp/src/components/TransactionModal/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 13f6ed82d..d6fd04303 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,9 +1,9 @@ import React, { useEffect } from "react" import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" -import { useSidebar } from "#/hooks" +import { useAppDispatch, useSidebar } from "#/hooks" import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" -import { setType } from "#/store/action-flow" +import { resetState, setType } from "#/store/action-flow" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" import withBaseModal from "../ModalRoot/withBaseModal" @@ -14,10 +14,11 @@ type TransactionModalProps = { function TransactionModalBase({ type }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() + const dispatch = useAppDispatch() useEffect(() => { - setType(type) - }, [type]) + dispatch(setType(type)) + }, [dispatch, type]) useEffect(() => { openSideBar() From 5f9f85dd534ce05a3377f03f63de0660d343af65 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:21:44 +0200 Subject: [PATCH 17/37] Reset state for `TransactionModal` --- dapp/src/components/TransactionModal/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index d6fd04303..5dc91b9f5 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -20,6 +20,13 @@ function TransactionModalBase({ type }: TransactionModalProps) { dispatch(setType(type)) }, [dispatch, type]) + // eslint-disable-next-line arrow-body-style + useEffect(() => { + return () => { + dispatch(resetState()) + } + }, [dispatch]) + useEffect(() => { openSideBar() return () => closeSidebar() From 15c3f9e96dcdcaba2781c219602521ca3dde045a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:23:45 +0200 Subject: [PATCH 18/37] Update value of delay for triggering Bitcoin transaction --- .../TransactionModal/ActiveStakingStep/DepositBTCModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 0940b2f63..3ef936def 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -18,7 +18,7 @@ import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" import { setStatus } from "#/store/action-flow" -const DELAY = ONE_SEC_IN_MILLISECONDS * 2 +const DELAY = ONE_SEC_IN_MILLISECONDS const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] From b9dd2d8a078b8afa237a949510694738fa2aebf6 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:36:05 +0200 Subject: [PATCH 19/37] Move `TransactionContext` to redux store --- .../TransactionModal/ActionFormModal.tsx | 13 +++---- .../ActiveStakingStep/DepositBTCModal.tsx | 4 +-- .../TransactionModal/ModalContentWrapper.tsx | 4 +-- .../src/components/TransactionModal/index.tsx | 16 ++++----- dapp/src/contexts/TransactionContext.tsx | 36 ------------------- dapp/src/contexts/index.tsx | 1 - dapp/src/hooks/index.ts | 1 - dapp/src/hooks/store/index.ts | 1 + .../hooks/store/useActionFlowTokenAmount.ts | 6 ++++ dapp/src/hooks/useTransactionContext.ts | 14 -------- .../store/action-flow/actionFlowSelectors.ts | 6 +++- dapp/src/store/action-flow/actionFlowSlice.ts | 13 +++++-- 12 files changed, 39 insertions(+), 76 deletions(-) delete mode 100644 dapp/src/contexts/TransactionContext.tsx create mode 100644 dapp/src/hooks/store/useActionFlowTokenAmount.ts delete mode 100644 dapp/src/hooks/useTransactionContext.ts diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e536d426e..e38c1684b 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,15 +1,12 @@ import React, { useCallback, useState } from "react" import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" -import { - useStakeFlowContext, - useTransactionContext, - useWalletContext, -} from "#/hooks" +import { useAppDispatch, useStakeFlowContext, useWalletContext } from "#/hooks" import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" +import { setTokenAmount } from "#/store/action-flow" const FORM_DATA: Record< ActionFlowType, @@ -34,8 +31,8 @@ const FORM_DATA: Record< function ActionFormModal({ type }: { type: ActionFlowType }) { const { btcAccount, ethAccount } = useWalletContext() - const { setTokenAmount } = useTransactionContext() const { initStake } = useStakeFlowContext() + const dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) @@ -59,14 +56,14 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { // TODO: Init unstake flow if (type === ACTION_FLOW_TYPES.STAKE) await handleInitStake() - setTokenAmount({ amount: values.amount, currency: "bitcoin" }) + dispatch(setTokenAmount({ amount: values.amount, currency: "bitcoin" })) } catch (error) { console.error(error) } finally { setIsLoading(false) } }, - [handleInitStake, setTokenAmount, type], + [dispatch, handleInitStake, type], ) const handleSubmitFormWrapper = useCallback( diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 3ef936def..b6843ce9e 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,11 +1,11 @@ import React, { useCallback } from "react" import { + useActionFlowTokenAmount, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, useStakeFlowContext, useToast, - useTransactionContext, useWalletContext, } from "#/hooks" import { logPromiseFailure } from "#/utils" @@ -24,7 +24,7 @@ const TOAST = TOASTS[TOAST_ID] export default function DepositBTCModal() { const { ethAccount } = useWalletContext() - const { tokenAmount } = useTransactionContext() + const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 914acf999..cfe99aea7 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,10 +1,10 @@ import React from "react" import { useActionFlowStatus, + useActionFlowTokenAmount, useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, - useTransactionContext, useWalletContext, } from "#/hooks" import { BitcoinIcon, EthereumIcon } from "#/assets/icons" @@ -26,7 +26,7 @@ export default function ModalContentWrapper({ const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() const status = useActionFlowStatus() const type = useActionFlowType() - const { tokenAmount } = useTransactionContext() + const tokenAmount = useActionFlowTokenAmount() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) return ( diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 5dc91b9f5..7fc1c88ad 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react" -import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" +import { StakeFlowProvider } from "#/contexts" import { useAppDispatch, useSidebar } from "#/hooks" import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" @@ -33,14 +33,12 @@ function TransactionModalBase({ type }: TransactionModalProps) { }, [closeSidebar, openSideBar]) return ( - - - - - - - - + + + + + + ) } diff --git a/dapp/src/contexts/TransactionContext.tsx b/dapp/src/contexts/TransactionContext.tsx deleted file mode 100644 index 8105fc841..000000000 --- a/dapp/src/contexts/TransactionContext.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useMemo, useState } from "react" -import { TokenAmount } from "#/types" - -type TransactionContextValue = { - tokenAmount?: TokenAmount - setTokenAmount: React.Dispatch> -} - -export const TransactionContext = createContext< - TransactionContextValue | undefined ->(undefined) - -export function TransactionContextProvider({ - children, -}: { - children: React.ReactNode -}): React.ReactElement { - const [tokenAmount, setTokenAmount] = useState( - undefined, - ) - - const contextValue: TransactionContextValue = - useMemo( - () => ({ - tokenAmount, - setTokenAmount, - }), - [tokenAmount], - ) - - return ( - - {children} - - ) -} diff --git a/dapp/src/contexts/index.tsx b/dapp/src/contexts/index.tsx index a3365497c..b4218417a 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,5 +3,4 @@ export * from "./WalletApiReactTransportProvider" export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" -export * from "./TransactionContext" export * from "./StakeFlowContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 150dda613..b3405e7be 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -8,7 +8,6 @@ export * from "./useRequestEthereumAccount" export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" -export * from "./useTransactionContext" export * from "./useTransactionDetails" export * from "./useDepositBTCTransaction" export * from "./useTransactionHistoryTable" diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index 7c30653b6..396d4e68f 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -6,5 +6,6 @@ export * from "./useMinDepositAmount" export * from "./useActionFlowType" export * from "./useActionFlowStatus" export * from "./useActionFlowActiveStep" +export * from "./useActionFlowTokenAmount" // TODO: Rename when the old hook is deleted. export { useActivities as useActivitiesNEW } from "./useActivities" diff --git a/dapp/src/hooks/store/useActionFlowTokenAmount.ts b/dapp/src/hooks/store/useActionFlowTokenAmount.ts new file mode 100644 index 000000000..f580df058 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowTokenAmount.ts @@ -0,0 +1,6 @@ +import { selectActionFlowTokenAmount } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowTokenAmount() { + return useAppSelector(selectActionFlowTokenAmount) +} diff --git a/dapp/src/hooks/useTransactionContext.ts b/dapp/src/hooks/useTransactionContext.ts deleted file mode 100644 index 41a8a8359..000000000 --- a/dapp/src/hooks/useTransactionContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from "react" -import { TransactionContext } from "#/contexts" - -export function useTransactionContext() { - const context = useContext(TransactionContext) - - if (!context) { - throw new Error( - "TransactionContext used outside of TransactionContext component", - ) - } - - return context -} diff --git a/dapp/src/store/action-flow/actionFlowSelectors.ts b/dapp/src/store/action-flow/actionFlowSelectors.ts index 6de0a44bb..a814d6290 100644 --- a/dapp/src/store/action-flow/actionFlowSelectors.ts +++ b/dapp/src/store/action-flow/actionFlowSelectors.ts @@ -1,4 +1,4 @@ -import { ActionFlowType, ProcessStatus } from "#/types" +import { ActionFlowType, ProcessStatus, TokenAmount } from "#/types" import { RootState } from ".." export const selectActionFlowType = (state: RootState): ActionFlowType => @@ -9,3 +9,7 @@ export const selectActionFlowActiveStep = (state: RootState): number => export const selectActionFlowStatus = (state: RootState): ProcessStatus => state.actionFlow.status + +export const selectActionFlowTokenAmount = ( + state: RootState, +): TokenAmount | undefined => state.actionFlow.tokenAmount diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts index 6a3b8a721..1866699c8 100644 --- a/dapp/src/store/action-flow/actionFlowSlice.ts +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -1,10 +1,16 @@ -import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { + ActionFlowType, + PROCESS_STATUSES, + ProcessStatus, + TokenAmount, +} from "#/types" import { PayloadAction, createSlice } from "@reduxjs/toolkit" type ActionFlowState = { type: ActionFlowType activeStep: number status: ProcessStatus + tokenAmount?: TokenAmount } const initialState: ActionFlowState = { @@ -26,6 +32,9 @@ export const actionFlowSlice = createSlice({ setStatus(state, action: PayloadAction) { state.status = action.payload }, + setTokenAmount(state, action: PayloadAction) { + state.tokenAmount = action.payload + }, goNextStep(state) { state.activeStep += 1 }, @@ -37,5 +46,5 @@ export const actionFlowSlice = createSlice({ }, }) -export const { setType, setStatus, goNextStep, resetState } = +export const { setType, setStatus, setTokenAmount, goNextStep, resetState } = actionFlowSlice.actions From af3135a8d20979b1bc44b5ccac9839a50bc571cc Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:44:38 +0200 Subject: [PATCH 20/37] Fix eslint issue with imports --- dapp/src/components/Layout.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dapp/src/components/Layout.tsx b/dapp/src/components/Layout.tsx index 528160b9c..43a0bc2d3 100644 --- a/dapp/src/components/Layout.tsx +++ b/dapp/src/components/Layout.tsx @@ -1,6 +1,5 @@ -import React from "react" +import React, { useState } from "react" import { AnimatePresence, motion, Variants } from "framer-motion" -import { useState } from "react" import { useLocation, useOutlet } from "react-router-dom" import DocsDrawer from "./DocsDrawer" import Header from "./Header" From 754f2b36e8725e9fcc48fb6bddf7766a835e5e93 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:46:05 +0200 Subject: [PATCH 21/37] Fix issue with resetting state for deposit flow --- dapp/src/store/action-flow/actionFlowSlice.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts index 1866699c8..a1fdf4c2f 100644 --- a/dapp/src/store/action-flow/actionFlowSlice.ts +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -17,6 +17,7 @@ const initialState: ActionFlowState = { type: "stake", activeStep: 1, status: PROCESS_STATUSES.IDLE, + tokenAmount: undefined, } export const actionFlowSlice = createSlice({ @@ -42,6 +43,7 @@ export const actionFlowSlice = createSlice({ state.type = initialState.type state.activeStep = initialState.activeStep state.status = initialState.status + state.tokenAmount = initialState.tokenAmount }, }, }) From 4b2d4ef7565dcabd6100a7024be53fce6cd1ef7a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:50:46 +0200 Subject: [PATCH 22/37] Fix eslint issue --- dapp/src/components/TransactionModal/ActionFormModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e38c1684b..dff2cc2a8 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -4,9 +4,9 @@ import { useAppDispatch, useStakeFlowContext, useWalletContext } from "#/hooks" import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" +import { setTokenAmount } from "#/store/action-flow" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" -import { setTokenAmount } from "#/store/action-flow" const FORM_DATA: Record< ActionFlowType, From d86f928fbb2eb4d385390d09ee6140e7c3cf6368 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 10:07:04 +0200 Subject: [PATCH 23/37] Fix issue for redux actions --- .../ActiveStakingStep/DepositBTCModal.tsx | 14 ++++++++------ .../StakingErrorModal/index.tsx | 11 ++++++++--- .../ActiveUnstakingStep/SignMessageModal.tsx | 16 +++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index b6843ce9e..f3fb591e5 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,6 +1,7 @@ import React, { useCallback } from "react" import { useActionFlowTokenAmount, + useAppDispatch, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, @@ -28,15 +29,16 @@ export default function DepositBTCModal() { const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() + const dispatch = useAppDispatch() const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [], + () => dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)), + [dispatch], ) const onStakeBTCError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleStake = useExecuteFunction( stake, @@ -46,10 +48,10 @@ export default function DepositBTCModal() { const onDepositBTCSuccess = useCallback(() => { closeToast(TOAST_ID) - setStatus(PROCESS_STATUSES.LOADING) + dispatch(setStatus(PROCESS_STATUSES.LOADING)) logPromiseFailure(handleStake()) - }, [closeToast, handleStake]) + }, [closeToast, dispatch, handleStake]) const showError = useCallback(() => { openToast({ diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index 1473e50ef..969a4e145 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useState } from "react" -import { useExecuteFunction, useStakeFlowContext } from "#/hooks" +import { + useAppDispatch, + useExecuteFunction, + useStakeFlowContext, +} from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" @@ -9,13 +13,14 @@ import LoadingModal from "../../LoadingModal" export default function StakingErrorModal() { const { stake } = useStakeFlowContext() + const dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) const [isServerError, setIsServerError] = useState(false) const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [], + () => dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)), + [dispatch], ) const onStakeBTCError = useCallback(() => setIsServerError(true), []) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index e05bc5bec..3a8950325 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react" -import { useExecuteFunction } from "#/hooks" +import { useAppDispatch, useExecuteFunction } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" @@ -7,14 +7,16 @@ import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { + const dispatch = useAppDispatch() + const onSignMessageSuccess = useCallback(() => { - setStatus(PROCESS_STATUSES.SUCCEEDED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) + }, [dispatch]) // TODO: After a failed attempt, we should display the message const onSignMessageError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleSignMessage = useExecuteFunction( // TODO: Use a correct function from the SDK @@ -24,13 +26,13 @@ export default function SignMessageModal() { ) const handleSignMessageWrapper = useCallback(() => { - setStatus(PROCESS_STATUSES.LOADING) + dispatch(setStatus(PROCESS_STATUSES.LOADING)) // TODO: Remove when SDK is ready setTimeout(() => { logPromiseFailure(handleSignMessage()) }, 5000) - }, [handleSignMessage]) + }, [dispatch, handleSignMessage]) return ( <> From e2cec5fd7385f814a08b878a3989e639b6e32d51 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 10:29:33 +0200 Subject: [PATCH 24/37] Improve styles for `SuccessModal` --- dapp/src/components/TransactionModal/SuccessModal.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 2c2a2c5de..00811bbc5 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -1,6 +1,5 @@ import React from "react" import { - Box, Button, HStack, ModalBody, @@ -28,7 +27,7 @@ const CONTENT: Record< header: "Deposit received", renderBody: (tokenAmount) => ( <> - + - + {/* TODO: Use correct tx hash and update styles */} From 5df4b8a258c420916336885763080274dd35a4a6 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 11:24:23 +0200 Subject: [PATCH 25/37] Create a special hook for triggering `TransactionModal` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After clicking on the “Deposit BTC” button, we should first call the Ledger Live native window to select an account. Once the account has been selected we open the right transaction modal to the user. --- .../TransactionModal/ActionFormModal.tsx | 4 +-- .../TransactionModal/ErrorModal.tsx | 4 +-- dapp/src/hooks/index.ts | 1 + dapp/src/hooks/useModal.ts | 11 ++++-- dapp/src/hooks/useRequestBitcoinAccount.ts | 2 +- dapp/src/hooks/useRequestEthereumAccount.ts | 2 +- dapp/src/hooks/useTransactionModal.ts | 34 +++++++++++++++++++ .../pages/DashboardPage/PositionDetails.tsx | 18 ++++------ .../LandingPage/components/HeroSection.tsx | 8 ++--- dapp/src/store/action-flow/actionFlowSlice.ts | 3 +- dapp/src/types/action-flow.ts | 4 +-- dapp/src/types/ledger-live-app.ts | 3 +- 12 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 dapp/src/hooks/useTransactionModal.ts diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index dff2cc2a8..13e6b1ae1 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -19,11 +19,11 @@ const FORM_DATA: Record< ) => React.ReactNode } > = { - stake: { + [ACTION_FLOW_TYPES.STAKE]: { header: "Deposit", FormComponent: StakeFormModal, }, - unstake: { + [ACTION_FLOW_TYPES.UNSTAKE]: { header: "Withdraw", FormComponent: UnstakeFormModal, }, diff --git a/dapp/src/components/TransactionModal/ErrorModal.tsx b/dapp/src/components/TransactionModal/ErrorModal.tsx index 92e343c31..64deb9259 100644 --- a/dapp/src/components/TransactionModal/ErrorModal.tsx +++ b/dapp/src/components/TransactionModal/ErrorModal.tsx @@ -1,9 +1,9 @@ import React from "react" -import { ActionFlowType } from "#/types" +import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" import StakingErrorModal from "./ActiveStakingStep/StakingErrorModal" export default function ErrorModal({ type }: { type: ActionFlowType }) { - if (type === "stake") return + if (type === ACTION_FLOW_TYPES.STAKE) return // TODO: Handle the case of unstake action return null } diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index b3405e7be..448d552e9 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -25,3 +25,4 @@ export * from "./useSize" export * from "./router" export * from "./useTransactionFee" export * from "./useModal" +export * from "./useTransactionModal" diff --git a/dapp/src/hooks/useModal.ts b/dapp/src/hooks/useModal.ts index 8d213e785..c1a38e3c0 100644 --- a/dapp/src/hooks/useModal.ts +++ b/dapp/src/hooks/useModal.ts @@ -5,6 +5,7 @@ import { selectModalType, } from "#/store/modal" import { ModalProps, ModalType } from "#/types" +import { useCallback } from "react" import { useAppDispatch } from "./store/useAppDispatch" import { useAppSelector } from "./store/useAppSelector" @@ -13,10 +14,14 @@ export function useModal() { const modalProps = useAppSelector(selectModalProps) const dispatch = useAppDispatch() - const handleOpenModal = (type: ModalType, props?: ModalProps) => - dispatch(openModal({ modalType: type, props })) + const handleOpenModal = useCallback( + (type: ModalType, props?: ModalProps) => { + dispatch(openModal({ modalType: type, props })) + }, + [dispatch], + ) - const handleCloseModal = () => dispatch(closeModal()) + const handleCloseModal = useCallback(() => dispatch(closeModal()), [dispatch]) return { modalType, diff --git a/dapp/src/hooks/useRequestBitcoinAccount.ts b/dapp/src/hooks/useRequestBitcoinAccount.ts index 7800adffb..834500e38 100644 --- a/dapp/src/hooks/useRequestBitcoinAccount.ts +++ b/dapp/src/hooks/useRequestBitcoinAccount.ts @@ -22,5 +22,5 @@ export function useRequestBitcoinAccount(): UseRequestAccountReturn { walletApiReactTransport.disconnect() }, [requestAccount, walletApiReactTransport]) - return { requestAccount: requestBitcoinAccount } + return { account, requestAccount: requestBitcoinAccount } } diff --git a/dapp/src/hooks/useRequestEthereumAccount.ts b/dapp/src/hooks/useRequestEthereumAccount.ts index 1fa5c1f16..e6ed33060 100644 --- a/dapp/src/hooks/useRequestEthereumAccount.ts +++ b/dapp/src/hooks/useRequestEthereumAccount.ts @@ -22,5 +22,5 @@ export function useRequestEthereumAccount(): UseRequestAccountReturn { walletApiReactTransport.disconnect() }, [requestAccount, walletApiReactTransport]) - return { requestAccount: requestEthereumAccount } + return { account, requestAccount: requestEthereumAccount } } diff --git a/dapp/src/hooks/useTransactionModal.ts b/dapp/src/hooks/useTransactionModal.ts new file mode 100644 index 000000000..de88a67cb --- /dev/null +++ b/dapp/src/hooks/useTransactionModal.ts @@ -0,0 +1,34 @@ +import { ActionFlowType, MODAL_TYPES } from "#/types" +import { useCallback, useEffect } from "react" +import { logPromiseFailure } from "#/utils" +import { useModal } from "./useModal" +import { useWalletContext } from "./useWalletContext" +import { useRequestBitcoinAccount } from "./useRequestBitcoinAccount" + +export function useTransactionModal(type: ActionFlowType) { + const { btcAccount } = useWalletContext() + const { account, requestAccount } = useRequestBitcoinAccount() + const { openModal } = useModal() + + const handleOpenModal = useCallback(() => { + openModal(MODAL_TYPES[type], { type }) + }, [openModal, type]) + + useEffect(() => { + // We should check the `account` here from `useRequestBitcoinAccount`. + // This will allow us to check there whether the account request action + // called earlier was successful. + // Checking the `btcAccount` may trigger a not needed modal opening. + if (account) { + handleOpenModal() + } + }, [account, handleOpenModal, openModal, type]) + + return useCallback(() => { + if (btcAccount) { + handleOpenModal() + } else { + logPromiseFailure(requestAccount()) + } + }, [btcAccount, handleOpenModal, requestAccount]) +} diff --git a/dapp/src/pages/DashboardPage/PositionDetails.tsx b/dapp/src/pages/DashboardPage/PositionDetails.tsx index 8a432b9f1..8089ce51f 100644 --- a/dapp/src/pages/DashboardPage/PositionDetails.tsx +++ b/dapp/src/pages/DashboardPage/PositionDetails.tsx @@ -9,15 +9,16 @@ import { } from "@chakra-ui/react" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { TextMd } from "#/components/shared/Typography" -import { MODAL_TYPES } from "#/types" +import { ACTION_FLOW_TYPES } from "#/types" import { useEstimatedBTCBalance } from "#/hooks/store" import { LiquidStakingTokenPopover } from "#/components/LiquidStakingTokenPopover" -import { useModal, useSize } from "#/hooks" +import { useSize, useTransactionModal } from "#/hooks" export default function PositionDetails(props: CardProps) { const estimatedBtcBalance = useEstimatedBTCBalance() const { ref, size } = useSize() - const { openModal } = useModal() + const openDepositModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) + const openWithdrawModal = useTransactionModal(ACTION_FLOW_TYPES.UNSTAKE) return ( @@ -40,17 +41,10 @@ export default function PositionDetails(props: CardProps) { /> - - diff --git a/dapp/src/pages/LandingPage/components/HeroSection.tsx b/dapp/src/pages/LandingPage/components/HeroSection.tsx index 280f2a710..bdba8106e 100644 --- a/dapp/src/pages/LandingPage/components/HeroSection.tsx +++ b/dapp/src/pages/LandingPage/components/HeroSection.tsx @@ -1,10 +1,10 @@ import React from "react" import { Button, Heading, VStack, Text } from "@chakra-ui/react" -import { useModal } from "#/hooks" -import { MODAL_TYPES } from "#/types" +import { useTransactionModal } from "#/hooks" +import { ACTION_FLOW_TYPES } from "#/types" export default function HeroSection() { - const { openModal } = useModal() + const openTransactionModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) return ( @@ -29,7 +29,7 @@ export default function HeroSection() { fontWeight="bold" lineHeight={6} h="auto" - onClick={() => openModal(MODAL_TYPES.STAKE, { type: "stake" })} + onClick={openTransactionModal} > Deposit BTC diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts index a1fdf4c2f..cf650d70c 100644 --- a/dapp/src/store/action-flow/actionFlowSlice.ts +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -1,4 +1,5 @@ import { + ACTION_FLOW_TYPES, ActionFlowType, PROCESS_STATUSES, ProcessStatus, @@ -14,7 +15,7 @@ type ActionFlowState = { } const initialState: ActionFlowState = { - type: "stake", + type: ACTION_FLOW_TYPES.STAKE, activeStep: 1, status: PROCESS_STATUSES.IDLE, tokenAmount: undefined, diff --git a/dapp/src/types/action-flow.ts b/dapp/src/types/action-flow.ts index a587b58b9..6e870647f 100644 --- a/dapp/src/types/action-flow.ts +++ b/dapp/src/types/action-flow.ts @@ -1,6 +1,6 @@ export const ACTION_FLOW_TYPES = { - STAKE: "stake", - UNSTAKE: "unstake", + STAKE: "STAKE", + UNSTAKE: "UNSTAKE", } as const export type ActionFlowType = diff --git a/dapp/src/types/ledger-live-app.ts b/dapp/src/types/ledger-live-app.ts index 8e82cb1a3..c2e452ca0 100644 --- a/dapp/src/types/ledger-live-app.ts +++ b/dapp/src/types/ledger-live-app.ts @@ -1,9 +1,10 @@ -import { WalletAPIClient } from "@ledgerhq/wallet-api-client" +import { Account, WalletAPIClient } from "@ledgerhq/wallet-api-client" export type RequestAccountParams = Parameters< WalletAPIClient["account"]["request"] > export type UseRequestAccountReturn = { + account: Account | null requestAccount: (...params: RequestAccountParams) => Promise } From 1e2383ec1edf4bc7af6f25834f31f08f3d54fb28 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 10 May 2024 13:44:46 +0200 Subject: [PATCH 26/37] Apply `PageLayout` to dashboard page --- dapp/src/pages/DashboardPage/index.tsx | 52 ++++++------------- .../OverviewPage/PageLayout/PageLayout.tsx | 15 +++--- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index ae1e36102..84d1b7f5f 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,42 +1,22 @@ import React from "react" -import { Flex, Grid, HStack, Switch } from "@chakra-ui/react" -import { TextSm } from "#/components/shared/Typography" -import { USD } from "#/constants" -import { chakraUnitToPx } from "#/theme/utils" -import PositionDetails from "./PositionDetails" -import Statistics from "./Statistics" -import TransactionHistory from "./TransactionHistory" -import { DocsCard } from "./DocsCard" -import { ActivityCarousel } from "./ActivityCarousel" +import { useWallet } from "#/hooks" +import { PageLayout, PageLayoutSidebar } from "./PageLayout" +import DashboardCard from "./DashboardCard" +import GrantedSeasonPassCard from "./GrantedSeasonPassCard" export default function DashboardPage() { - return ( - - - {/* TODO: Handle click actions */} - - Show values in {USD.symbol} - + const { bitcoin } = useWallet() + const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" - - - - - - - - - - + return ( + + + + } + > + + ) } diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx index b1d996294..249d0116f 100644 --- a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx @@ -4,18 +4,21 @@ import PageLayoutSidebar from "./PageLayoutSidebar" type PageLayoutProps = Omit & { children: React.ReactNode - leftSidebar: React.ReactNode - rightSidebar: React.ReactNode + leftSidebar?: React.ReactNode + rightSidebar?: React.ReactNode } function PageLayout(props: PageLayoutProps) { const { children, leftSidebar, rightSidebar, ...restProps } = props - const isSidebarPropsInvalid = [leftSidebar, rightSidebar].some( - (value) => !React.isValidElement(value) || value.type !== PageLayoutSidebar, - ) + const isSidebarPropsInvalid = [leftSidebar, rightSidebar] + .filter(Boolean) + .some( + (value) => + !React.isValidElement(value) || value.type !== PageLayoutSidebar, + ) if (isSidebarPropsInvalid) { - throw new Error("Sidebars must be wrapped with `PageLayout.Sidebar`.") + throw new Error("Sidebar content must be wrapped with `PageLayoutSidebar`.") } return ( From 720d91c02abbc87405bcc111d63762efa2feb8f5 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 10 May 2024 13:46:53 +0200 Subject: [PATCH 27/37] Resolve merge errors --- .../{OverviewPage => DashboardPage}/PageLayout/PageLayout.tsx | 0 .../PageLayout/PageLayoutSidebar.tsx | 0 .../pages/{OverviewPage => DashboardPage}/PageLayout/index.ts | 0 dapp/src/theme/index.ts | 1 + 4 files changed, 1 insertion(+) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/PageLayout.tsx (100%) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/PageLayoutSidebar.tsx (100%) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/index.ts (100%) diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx rename to dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx rename to dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx diff --git a/dapp/src/pages/OverviewPage/PageLayout/index.ts b/dapp/src/pages/DashboardPage/PageLayout/index.ts similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/index.ts rename to dapp/src/pages/DashboardPage/PageLayout/index.ts diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 55164bbdf..4338c5039 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -47,6 +47,7 @@ const defaultTheme = { styles, breakpoints: { "2.5xl": "100.5rem", // 1608px + }, space: { 13: "3.25rem", 15: "3.75rem", From 0a420d1f030e4ebca6e03665018e4e5b059ec126 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 14 May 2024 08:24:08 +0200 Subject: [PATCH 28/37] Simplify form data for ActionFormModal component --- .../TransactionModal/ActionFormModal.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e536d426e..70efc7a1b 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React, { ReactNode, useCallback, useState } from "react" import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" import { useStakeFlowContext, @@ -14,21 +14,21 @@ import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" const FORM_DATA: Record< ActionFlowType, { - header: string - FormComponent: ( + heading: string + renderComponent: ( props: React.ComponentProps< typeof StakeFormModal | typeof UnstakeFormModal >, - ) => React.ReactNode + ) => ReactNode } > = { stake: { - header: "Deposit", - FormComponent: StakeFormModal, + heading: "Deposit", + renderComponent: StakeFormModal, }, unstake: { - header: "Withdraw", - FormComponent: UnstakeFormModal, + heading: "Withdraw", + renderComponent: UnstakeFormModal, }, } @@ -39,7 +39,7 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { const [isLoading, setIsLoading] = useState(false) - const { header, FormComponent } = FORM_DATA[type] + const { heading, renderComponent } = FORM_DATA[type] const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address @@ -78,10 +78,10 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { return ( <> {!isLoading && } - {header} + {heading} - + {renderComponent({ onSubmitForm: handleSubmitFormWrapper })} From a14030fd9d0cab789dd0785b97f009fe025ee936 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 14 May 2024 08:33:33 +0200 Subject: [PATCH 29/37] Create a shared type for forms --- .../components/TransactionModal/ActionFormModal.tsx | 12 +++++------- .../ActiveStakingStep/StakeFormModal/index.tsx | 5 ++--- .../ActiveUnstakingStep/UnstakeFormModal/index.tsx | 5 ++--- dapp/src/components/shared/TokenAmountForm/index.tsx | 5 +++-- dapp/src/types/form.ts | 3 +++ dapp/src/types/index.ts | 1 + 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 dapp/src/types/form.ts diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 70efc7a1b..cc0ac9b67 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -5,7 +5,7 @@ import { useTransactionContext, useWalletContext, } from "#/hooks" -import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" +import { ACTION_FLOW_TYPES, ActionFlowType, BaseFormProps } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" @@ -15,11 +15,7 @@ const FORM_DATA: Record< ActionFlowType, { heading: string - renderComponent: ( - props: React.ComponentProps< - typeof StakeFormModal | typeof UnstakeFormModal - >, - ) => ReactNode + renderComponent: (props: BaseFormProps) => ReactNode } > = { stake: { @@ -81,7 +77,9 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { {heading} - {renderComponent({ onSubmitForm: handleSubmitFormWrapper })} + {renderComponent({ + onSubmitForm: handleSubmitFormWrapper, + })} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 24c590b9d..220051905 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -3,13 +3,12 @@ import TokenAmountForm from "#/components/shared/TokenAmountForm" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { useMinDepositAmount, useWalletContext } from "#/hooks" import { FormSubmitButton } from "#/components/shared/Form" +import { BaseFormProps } from "#/types" import StakeDetails from "./StakeDetails" function StakeFormModal({ onSubmitForm, -}: { - onSubmitForm: (values: TokenAmountFormValues) => void -}) { +}: BaseFormProps) { const minDepositAmount = useMinDepositAmount() const { btcAccount } = useWalletContext() const tokenBalance = BigInt(btcAccount?.balance.toString() ?? "0") diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx index 7cdbaad15..17dc1f07b 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx @@ -6,6 +6,7 @@ import { TextMd, TextSm } from "#/components/shared/Typography" import Spinner from "#/components/shared/Spinner" import { FormSubmitButton } from "#/components/shared/Form" import { useMinDepositAmount } from "#/hooks" +import { BaseFormProps } from "#/types" import UnstakeDetails from "./UnstakeDetails" // TODO: Use a position amount @@ -13,9 +14,7 @@ const MOCK_POSITION_AMOUNT = BigInt("2398567898") function UnstakeFormModal({ onSubmitForm, -}: { - onSubmitForm: (values: TokenAmountFormValues) => void -}) { +}: BaseFormProps) { const minDepositAmount = useMinDepositAmount() return ( diff --git a/dapp/src/components/shared/TokenAmountForm/index.tsx b/dapp/src/components/shared/TokenAmountForm/index.tsx index 80850c4e0..1aa49ad9d 100644 --- a/dapp/src/components/shared/TokenAmountForm/index.tsx +++ b/dapp/src/components/shared/TokenAmountForm/index.tsx @@ -1,14 +1,15 @@ import { FormikErrors, withFormik } from "formik" import { getErrorsObj, validateTokenAmount } from "#/utils" +import { BaseFormProps } from "#/types" import TokenAmountFormBase, { TokenAmountFormBaseProps, TokenAmountFormValues, } from "./TokenAmountFormBase" type TokenAmountFormProps = { - onSubmitForm: (values: TokenAmountFormValues) => void minTokenAmount: bigint -} & TokenAmountFormBaseProps +} & TokenAmountFormBaseProps & + BaseFormProps const TokenAmountForm = withFormik( { diff --git a/dapp/src/types/form.ts b/dapp/src/types/form.ts new file mode 100644 index 000000000..6a4526228 --- /dev/null +++ b/dapp/src/types/form.ts @@ -0,0 +1,3 @@ +export type BaseFormProps = { + onSubmitForm: (values: T) => void +} diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index dc645ffc8..0623cda8a 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -17,3 +17,4 @@ export * from "./core" export * from "./fee" export * from "./navigation" export * from "./subgraphAPI" +export * from "./form" From a533fbb50460bec2227556507d17cafda5710861 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 14:47:33 +0200 Subject: [PATCH 30/37] Refactor `PageLayout` component --- .../DashboardPage/PageLayout/PageLayout.tsx | 35 +++---------------- .../PageLayout/PageLayoutColumn.tsx | 29 +++++++++++++++ .../PageLayout/PageLayoutSidebar.tsx | 8 ----- .../pages/DashboardPage/PageLayout/index.ts | 2 +- dapp/src/pages/DashboardPage/index.tsx | 18 +++++----- 5 files changed, 43 insertions(+), 49 deletions(-) create mode 100644 dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx delete mode 100644 dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx index 249d0116f..0303ae3b9 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx @@ -1,25 +1,8 @@ import React from "react" -import { Grid, GridProps, Box } from "@chakra-ui/react" -import PageLayoutSidebar from "./PageLayoutSidebar" +import { Grid, GridProps } from "@chakra-ui/react" -type PageLayoutProps = Omit & { - children: React.ReactNode - leftSidebar?: React.ReactNode - rightSidebar?: React.ReactNode -} - -function PageLayout(props: PageLayoutProps) { - const { children, leftSidebar, rightSidebar, ...restProps } = props - const isSidebarPropsInvalid = [leftSidebar, rightSidebar] - .filter(Boolean) - .some( - (value) => - !React.isValidElement(value) || value.type !== PageLayoutSidebar, - ) - - if (isSidebarPropsInvalid) { - throw new Error("Sidebar content must be wrapped with `PageLayoutSidebar`.") - } +function PageLayout(props: GridProps) { + const { children, ...restProps } = props return ( - - {children} - - {leftSidebar} - {rightSidebar} + {children} ) } diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx new file mode 100644 index 000000000..5e5b12872 --- /dev/null +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { StackProps, VStack } from "@chakra-ui/react" + +type PageLayoutColumnProps = StackProps & { + isMain?: boolean +} + +function PageLayoutColumn(props: PageLayoutColumnProps) { + const { isMain = false, ...restProps } = props + + return ( + + ) +} + +export default PageLayoutColumn diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx deleted file mode 100644 index 6abb979e9..000000000 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react" -import { StackProps, VStack } from "@chakra-ui/react" - -function PageLayoutSidebar(props: StackProps) { - return -} - -export default PageLayoutSidebar diff --git a/dapp/src/pages/DashboardPage/PageLayout/index.ts b/dapp/src/pages/DashboardPage/PageLayout/index.ts index 4121f6e16..8197d5e88 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/index.ts +++ b/dapp/src/pages/DashboardPage/PageLayout/index.ts @@ -1,2 +1,2 @@ export { default as PageLayout } from "./PageLayout" -export { default as PageLayoutSidebar } from "./PageLayoutSidebar" +export { default as PageLayoutColumn } from "./PageLayoutColumn" diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 84d1b7f5f..40d5e80ba 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,6 +1,6 @@ import React from "react" import { useWallet } from "#/hooks" -import { PageLayout, PageLayoutSidebar } from "./PageLayout" +import { PageLayout, PageLayoutColumn } from "./PageLayout" import DashboardCard from "./DashboardCard" import GrantedSeasonPassCard from "./GrantedSeasonPassCard" @@ -9,14 +9,14 @@ export default function DashboardPage() { const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" return ( - - - - } - > - + + + + + + + + ) } From 590011fffd2b4634c76e8cce9b59afe23906329f Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 14:52:01 +0200 Subject: [PATCH 31/37] Update layout Resolve bug with empty `ul` element of `ActivityList` --- .../shared/ActivitiesList/ActivitiesList.tsx | 4 ++-- .../pages/DashboardPage/GrantedSeasonPassCard.tsx | 14 +++----------- dapp/src/pages/DashboardPage/index.tsx | 4 +++- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx index d4df818ed..755d2857f 100644 --- a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx +++ b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx @@ -23,7 +23,7 @@ function ActivitiesList(props: ListProps) { setDismissedActivities((prev) => [...prev, txHash]) } - return ( + return activities.length > 0 ? ( {activities.map((item) => ( @@ -47,7 +47,7 @@ function ActivitiesList(props: ListProps) { ))} - ) + ) : null } export default ActivitiesList diff --git a/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx index 8ad864e54..36fb10538 100644 --- a/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx @@ -10,19 +10,11 @@ import { import { IconDiscountCheckFilled, IconLock } from "@tabler/icons-react" import { TextMd } from "#/components/shared/Typography" -type GrantedSeasonPassCardProps = CardProps & { - heading: string -} - -export default function GrantedSeasonPassCard( - props: GrantedSeasonPassCardProps, -) { - const { heading, ...restProps } = props - +export default function GrantedSeasonPassCard(props: CardProps) { return ( - + - {heading} + Season 2. Pre-launch staking - + + ) From eed479f4ff7ca5ead98b7676050119816df475b2 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:17:55 +0200 Subject: [PATCH 32/37] Add Dashboard's gamification placeholder Hid season stats --- .../images/gamification-placeholder.svg | 1 + dapp/src/pages/DashboardPage/index.tsx | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 dapp/src/assets/images/gamification-placeholder.svg diff --git a/dapp/src/assets/images/gamification-placeholder.svg b/dapp/src/assets/images/gamification-placeholder.svg new file mode 100644 index 000000000..22847cb95 --- /dev/null +++ b/dapp/src/assets/images/gamification-placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index c583afd21..311cb3730 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,10 +1,16 @@ import React from "react" import { useWallet } from "#/hooks" +import { Icon, Image, VStack } from "@chakra-ui/react" +import gamificationPlaceholderImage from "#/assets/images/gamification-placeholder.svg" +import { AcreLogo } from "#/assets/icons" +import { TextMd } from "#/components/shared/Typography" import { PageLayout, PageLayoutColumn } from "./PageLayout" import DashboardCard from "./DashboardCard" import GrantedSeasonPassCard from "./GrantedSeasonPassCard" import { CurrentSeasonCard } from "./CurrentSeasonCard" +// TODO: Remove placeholder image and replace with actual gamification content + export default function DashboardPage() { const { bitcoin } = useWallet() const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" @@ -16,9 +22,27 @@ export default function DashboardPage() { - + + + + + + Coming soon... + + Gamification placeholder + ) } From 39ffbc7499c4b13c702520fa5f51812b736dce66 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:20:16 +0200 Subject: [PATCH 33/37] Use hook instead of getting value manually --- dapp/src/pages/DashboardPage/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 311cb3730..0f40289ca 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,5 +1,5 @@ import React from "react" -import { useWallet } from "#/hooks" +import { useEstimatedBTCBalance } from "#/hooks" import { Icon, Image, VStack } from "@chakra-ui/react" import gamificationPlaceholderImage from "#/assets/images/gamification-placeholder.svg" import { AcreLogo } from "#/assets/icons" @@ -12,8 +12,7 @@ import { CurrentSeasonCard } from "./CurrentSeasonCard" // TODO: Remove placeholder image and replace with actual gamification content export default function DashboardPage() { - const { bitcoin } = useWallet() - const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" + const bitcoinWalletBalance = useEstimatedBTCBalance() return ( From 2a72fc3915c68446bf1f993edd4e9d78a075890d Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:53:44 +0200 Subject: [PATCH 34/37] Adjust page layout breakpoints --- dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx | 5 ++--- dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx | 4 ++-- dapp/src/pages/DashboardPage/index.tsx | 1 + dapp/src/theme/index.ts | 3 --- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx index 0303ae3b9..041416507 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx @@ -13,9 +13,8 @@ function PageLayout(props: GridProps) { gridTemplateColumns={{ base: "1fr", md: "repeat(2, 1fr)", - xl: "0.76fr auto", - "2.5xl": - "minmax(358px, 0.25fr) minmax(748px, 1fr) minmax(358px, 0.25fr)", + lg: "1fr 0.5fr", + xl: "minmax(358px, auto) 1fr minmax(358px, auto)", }} {...restProps} > diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx index 5e5b12872..78d26a4d4 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx @@ -14,9 +14,9 @@ function PageLayoutColumn(props: PageLayoutColumnProps) { gridArea={ isMain ? { - xl: "1 / 1 / 3 / 2", - "2.5xl": "1 / 2 / -1 / 3", base: "1 / 1 / -1 / -1", + lg: "1 / 1 / 3 / 2", + xl: "1 / 2 / -1 / 3", } : undefined } diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 0f40289ca..f9dff67ac 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -31,6 +31,7 @@ export default function DashboardPage() { Coming soon... Date: Wed, 15 May 2024 07:22:41 +0200 Subject: [PATCH 35/37] Open `TransactionModal` from new dashboard --- dapp/src/pages/DashboardPage/DashboardCard.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dapp/src/pages/DashboardPage/DashboardCard.tsx b/dapp/src/pages/DashboardPage/DashboardCard.tsx index 5b97bd116..459132cc8 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -15,8 +15,9 @@ import { TextMd } from "#/components/shared/Typography" import IconTag from "#/components/shared/IconTag" import { BoostArrowIcon } from "#/assets/icons" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" -import { AmountType } from "#/types" +import { AmountType, MODAL_TYPES } from "#/types" import { ActivitiesList } from "#/components/shared/ActivitiesList" +import { useModal } from "#/hooks" const buttonStyles: ButtonProps = { size: "lg", @@ -35,6 +36,9 @@ type DashboardCardProps = CardProps & { export default function DashboardCard(props: DashboardCardProps) { const { bitcoinAmount, positionPercentage, ...restProps } = props + + const { openModal } = useModal() + return ( @@ -83,7 +87,12 @@ export default function DashboardCard(props: DashboardCardProps) { - + From 6745b0c3f25908864e3dc3597025d4c386f8ab73 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 15 May 2024 07:35:06 +0200 Subject: [PATCH 36/37] Use `useTransactionModal` in new dashboard --- dapp/src/pages/DashboardPage/DashboardCard.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/dapp/src/pages/DashboardPage/DashboardCard.tsx b/dapp/src/pages/DashboardPage/DashboardCard.tsx index 459132cc8..ee6caf612 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -15,9 +15,9 @@ import { TextMd } from "#/components/shared/Typography" import IconTag from "#/components/shared/IconTag" import { BoostArrowIcon } from "#/assets/icons" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" -import { AmountType, MODAL_TYPES } from "#/types" +import { ACTION_FLOW_TYPES, AmountType } from "#/types" import { ActivitiesList } from "#/components/shared/ActivitiesList" -import { useModal } from "#/hooks" +import { useTransactionModal } from "#/hooks" const buttonStyles: ButtonProps = { size: "lg", @@ -37,7 +37,7 @@ type DashboardCardProps = CardProps & { export default function DashboardCard(props: DashboardCardProps) { const { bitcoinAmount, positionPercentage, ...restProps } = props - const { openModal } = useModal() + const openDepositModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) return ( @@ -87,10 +87,7 @@ export default function DashboardCard(props: DashboardCardProps) { -