diff --git a/dapp/src/components/Sidebar.tsx b/dapp/src/components/Sidebar.tsx index d90c972f0..d2e0d5ef9 100644 --- a/dapp/src/components/Sidebar.tsx +++ b/dapp/src/components/Sidebar.tsx @@ -46,6 +46,7 @@ export default function Sidebar() { {featureFlags.GAMIFICATION_ENABLED && ( <> + {/* TODO: Update the component when logic of losing rewards is ready */} Rewards you’ll unlock {BENEFITS.map(({ name, imageSrc }) => ( diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index b7e48d79e..4b2728486 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -12,16 +12,16 @@ const FORM_DATA: Record< ActionFlowType, { heading: string - renderComponent: (props: BaseFormProps) => ReactNode + FormComponent: (props: BaseFormProps) => ReactNode } > = { [ACTION_FLOW_TYPES.STAKE]: { heading: "Deposit", - renderComponent: StakeFormModal, + FormComponent: StakeFormModal, }, [ACTION_FLOW_TYPES.UNSTAKE]: { heading: "Withdraw", - renderComponent: UnstakeFormModal, + FormComponent: UnstakeFormModal, }, } @@ -31,7 +31,7 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { const [isLoading, setIsLoading] = useState(false) - const { heading, renderComponent } = FORM_DATA[type] + const { heading, FormComponent } = FORM_DATA[type] const handleInitStake = useCallback(async () => { await initStake() @@ -68,9 +68,7 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { {heading} - {renderComponent({ - onSubmitForm: handleSubmitFormWrapper, - })} + diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index aa15e7184..489dce12d 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from "react" import { + useActionFlowPause, useActionFlowTokenAmount, useAppDispatch, useDepositBTCTransaction, @@ -7,22 +8,20 @@ import { useStakeFlowContext, useVerifyDepositAddress, } from "#/hooks" -import { logPromiseFailure } from "#/utils" -import { PROCESS_STATUSES } from "#/types" -import { ModalBody, ModalHeader, Highlight, useTimeout } from "@chakra-ui/react" -import Spinner from "#/components/shared/Spinner" +import { isLedgerLiveError, logPromiseFailure } from "#/utils" +import { PROCESS_STATUSES, LedgerLiveError } from "#/types" +import { Highlight } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import { CardAlert } from "#/components/shared/alerts" -import { ONE_SEC_IN_MILLISECONDS } from "#/constants" import { setStatus, setTxHash } from "#/store/action-flow" - -const DELAY = ONE_SEC_IN_MILLISECONDS +import TriggerTransactionModal from "../TriggerTransactionModal" export default function DepositBTCModal() { const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const verifyDepositAddress = useVerifyDepositAddress() const dispatch = useAppDispatch() + const { handlePause } = useActionFlowPause() const onStakeBTCSuccess = useCallback(() => { dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) @@ -45,9 +44,22 @@ export default function DepositBTCModal() { }, [dispatch, handleStake]) // TODO: Handle when the function fails - const showError = useCallback(() => {}, []) + const showError = useCallback((error?: LedgerLiveError) => { + console.error(error) + }, []) + + const onDepositBTCError = useCallback( + (error: unknown) => { + if (!isLedgerLiveError(error)) return - const onDepositBTCError = useCallback(() => showError(), [showError]) + const isInterrupted = + error.message && error.message.includes("Signature interrupted by user") + if (isInterrupted) handlePause() + + showError(error) + }, + [showError, handlePause], + ) const { sendBitcoinTransaction, transactionHash } = useDepositBTCTransaction( onDepositBTCSuccess, @@ -82,23 +94,16 @@ export default function DepositBTCModal() { logPromiseFailure(handledDepositBTC()) }, [handledDepositBTC]) - useTimeout(handledDepositBTCWrapper, DELAY) - return ( - <> - Waiting transaction... - - - Please complete the transaction in your wallet. - - - - You will receive your Rewards once the deposit transaction is - completed. - - - - - + + + + + You will receive your Rewards once the deposit transaction is + completed. + + + + ) } diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx index 416d691b0..a4cff53cf 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx @@ -2,11 +2,10 @@ import React from "react" import { List } from "@chakra-ui/react" import TransactionDetailsAmountItem from "#/components/shared/TransactionDetails/AmountItem" import FeesDetailsAmountItem from "#/components/shared/FeesDetails/FeesItem" -import { useTokenAmountFormValue } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" +import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { FeesTooltip } from "#/components/TransactionModal/FeesTooltip" import { useTransactionDetails } from "#/hooks" import { CurrencyType, DepositFee } from "#/types" -import { useTransactionFee } from "#/hooks/useTransactionFee" const mapDepositFeeToLabel = (feeId: keyof DepositFee) => { switch (feeId) { @@ -19,22 +18,12 @@ const mapDepositFeeToLabel = (feeId: keyof DepositFee) => { } } -function StakeDetails({ - currency, - minTokenAmount, - maxTokenAmount, -}: { - currency: CurrencyType - minTokenAmount: bigint - maxTokenAmount: bigint -}) { - const value = useTokenAmountFormValue() ?? 0n - const isMaximumValueExceeded = value > maxTokenAmount - const isMinimumValueFulfilled = value >= minTokenAmount +function StakeDetails({ currency }: { currency: CurrencyType }) { + const { value, isValid } = useTokenAmountField() // Let's not calculate the details of the transaction when the value is not valid. - const amount = !isMaximumValueExceeded && isMinimumValueFulfilled ? value : 0n + const amount = isValid ? value : 0n const details = useTransactionDetails(amount) - const { total, ...restFees } = useTransactionFee(amount) + const { total, ...restFees } = details.transactionFee return ( @@ -42,7 +31,7 @@ function StakeDetails({ label="Amount to be staked" from={{ currency, - amount: details?.btcAmount, + amount: details.amount, }} to={{ currency: "usd", @@ -67,7 +56,7 @@ function StakeDetails({ label="Approximately staked tokens" from={{ currency, - amount: details?.estimatedAmount, + amount: details.estimatedAmount, }} to={{ currency: "usd", diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 220051905..a0d50742c 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -22,11 +22,7 @@ function StakeFormModal({ minTokenAmount={minDepositAmount} onSubmitForm={onSubmitForm} > - + Stake ) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index 3a8950325..7a0590a9e 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,14 +1,14 @@ import React, { useCallback } from "react" -import { useAppDispatch, useExecuteFunction } from "#/hooks" +import { useAppDispatch, useExecuteFunction, useModal } from "#/hooks" import { PROCESS_STATUSES } from "#/types" -import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" -import { TextMd } from "#/components/shared/Typography" +import { Button } from "@chakra-ui/react" import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" +import TriggerTransactionModal from "../TriggerTransactionModal" export default function SignMessageModal() { const dispatch = useAppDispatch() - + const { closeModal } = useModal() const onSignMessageSuccess = useCallback(() => { dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) }, [dispatch]) @@ -35,19 +35,10 @@ export default function SignMessageModal() { }, [dispatch, handleSignMessage]) return ( - <> - Sign message - - - 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/ActiveUnstakingStep/UnstakeFormModal/UnstakeDetails.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/UnstakeDetails.tsx index 844fe9c43..355384a6c 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/UnstakeDetails.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/UnstakeDetails.tsx @@ -1,28 +1,42 @@ import React from "react" -import { List } from "@chakra-ui/react" +import { Flex, List } from "@chakra-ui/react" import TransactionDetailsAmountItem from "#/components/shared/TransactionDetails/AmountItem" -import { useTokenAmountFormValue } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" +import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { useTransactionDetails } from "#/hooks" import { CurrencyType } from "#/types" +import { featureFlags } from "#/constants" +import WithdrawWarning from "./WithdrawWarning" -function UnstakeDetails({ currency }: { currency: CurrencyType }) { - const value = useTokenAmountFormValue() - const details = useTransactionDetails(value ?? 0n) +function UnstakeDetails({ + balance, + currency, +}: { + balance: bigint + currency: CurrencyType +}) { + const { value, isValid } = useTokenAmountField() + // Let's not calculate the details of the transaction when the value is not valid. + const amount = isValid ? value : 0n + const details = useTransactionDetails(amount) return ( - - - {/* TODO: Uncomment when unstaking fees are ready. */} - {/* + {featureFlags.GAMIFICATION_ENABLED && ( + + )} + + + {/* TODO: Uncomment when unstaking fees are ready. */} + {/* } @@ -34,17 +48,18 @@ function UnstakeDetails({ currency }: { currency: CurrencyType }) { currency: "usd", }} /> */} - - + + + ) } diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/WithdrawWarning.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/WithdrawWarning.tsx new file mode 100644 index 000000000..b5b5806dc --- /dev/null +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/WithdrawWarning.tsx @@ -0,0 +1,60 @@ +import React from "react" +import { Box } from "@chakra-ui/react" +import { CurrencyType } from "#/types" +import { MINIMUM_BALANCE } from "#/constants" +import { formatSatoshiAmount, getCurrencyByType } from "#/utils" +import { CardAlert } from "#/components/shared/alerts" +import { TextMd } from "#/components/shared/Typography" +import { useTokenAmountField } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" + +function WithdrawWarning({ + balance, + currency, +}: { + balance: bigint + currency: CurrencyType +}) { + const { value, isValid } = useTokenAmountField() + const amount = value ?? 0n + + const { symbol } = getCurrencyByType(currency) + + const minimumBalanceText = `${formatSatoshiAmount( + MINIMUM_BALANCE, + )} ${symbol} ` + + const newBalance = balance - amount + const isMinimumBalanceExceeded = newBalance < MINIMUM_BALANCE + + if (isMinimumBalanceExceeded && isValid) { + return ( + // TODO: Update global styles for the Alert component + // Previously, we distinguished more types of alerts. + // The following styles should be moved to global styles and unneeded parts removed. + + + The new balance is below the required minimum of + {minimumBalanceText}. Withdrawing your funds + will result in the loss of your current rewards. + + + ) + } + + return ( + + + A minimum balance of + {minimumBalanceText} is required to keep all + rewards active. + + + ) +} + +export default WithdrawWarning diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx index 17dc1f07b..8ff6be439 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx @@ -1,52 +1,29 @@ import React from "react" -import { Card, CardBody, Flex, HStack } from "@chakra-ui/react" import TokenAmountForm from "#/components/shared/TokenAmountForm" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" -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 { useEstimatedBTCBalance, useMinDepositAmount } from "#/hooks" import UnstakeDetails from "./UnstakeDetails" -// TODO: Use a position amount -const MOCK_POSITION_AMOUNT = BigInt("2398567898") - function UnstakeFormModal({ onSubmitForm, }: BaseFormProps) { - const minDepositAmount = useMinDepositAmount() + const balance = useEstimatedBTCBalance() + // TODO: Use the right value from SDK when the logic for the withdraw will be ready + const minTokenAmount = useMinDepositAmount() return ( - - - - - - Next batch starts in... - - {/* TODO: Use a correct time for batch */} - - 23h 34m left - - - - - - Extra informative text. Maximize your earnings by using tBTC to - deposit and redeem BTC in DeFi! - - - - - Unstake + + Withdraw ) } diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 8ff6d3477..7d42dd72e 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -2,7 +2,6 @@ import React from "react" import { useActionFlowStatus, useActionFlowTokenAmount, - useActionFlowTxHash, useActionFlowType, useRequestBitcoinAccount, useWalletContext, @@ -15,6 +14,7 @@ import ErrorModal from "./ErrorModal" import LoadingModal from "./LoadingModal" import MissingAccountModal from "./MissingAccountModal" import SuccessModal from "./SuccessModal" +import ResumeModal from "./ResumeModal" export default function ModalContentWrapper({ children, @@ -26,7 +26,6 @@ export default function ModalContentWrapper({ const status = useActionFlowStatus() const type = useActionFlowType() const tokenAmount = useActionFlowTokenAmount() - const txHash = useActionFlowTxHash() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) return ( @@ -41,12 +40,11 @@ export default function ModalContentWrapper({ if (status === PROCESS_STATUSES.LOADING) return - if (status === PROCESS_STATUSES.SUCCEEDED && txHash) - return ( - - ) + if (status === PROCESS_STATUSES.SUCCEEDED) return if (status === PROCESS_STATUSES.FAILED) return + if (status === PROCESS_STATUSES.PAUSED) return + return children } diff --git a/dapp/src/components/TransactionModal/ResumeModal.tsx b/dapp/src/components/TransactionModal/ResumeModal.tsx new file mode 100644 index 000000000..3d63b6cb9 --- /dev/null +++ b/dapp/src/components/TransactionModal/ResumeModal.tsx @@ -0,0 +1,42 @@ +import React from "react" +import { + ModalHeader, + ModalBody, + ModalFooter, + Button, + HStack, +} from "@chakra-ui/react" + +import Spinner from "#/components/shared/Spinner" +import { PauseIcon } from "#/assets/icons" +import { TextMd } from "#/components/shared/Typography" +import { useActionFlowPause, useModal } from "#/hooks" + +export default function ResumeModal() { + const { handleResume } = useActionFlowPause() + const { closeModal } = useModal() + + return ( + <> + + Paused + + + + + + + + Are your sure you want to cancel? + + + + + + + ) +} diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 937e0286b..f3978f465 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { ReactNode } from "react" import { Button, HStack, @@ -9,14 +9,15 @@ import { VStack, } from "@chakra-ui/react" import { LoadingSpinnerSuccessIcon } from "#/assets/icons" -import { useAllActivitiesCount, useFetchDeposits, useModal } from "#/hooks" -import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { - ACTION_FLOW_TYPES, - ActionFlowType, - MODAL_TYPES, - TokenAmount, -} from "#/types" + useActionFlowTokenAmount, + useActionFlowTxHash, + useModal, + useAllActivitiesCount, + useFetchDeposits, +} from "#/hooks" +import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" +import { ACTION_FLOW_TYPES, ActionFlowType, MODAL_TYPES } from "#/types" import { useNavigate } from "react-router-dom" import { routerPath } from "#/router/path" import { IconArrowUpRight } from "@tabler/icons-react" @@ -26,18 +27,13 @@ import { TextMd } from "../shared/Typography" import Spinner from "../shared/Spinner" import BlockExplorerLink from "../shared/BlockExplorerLink" -const CONTENT: Record< - ActionFlowType, - { - header: string - renderBody: (tokenAmount: TokenAmount, txHash: string) => React.ReactNode - footer: string - } -> = { - [ACTION_FLOW_TYPES.STAKE]: { - header: "Deposit received", - renderBody: (tokenAmount, txHash) => ( - <> +function StakeContent() { + const tokenAmount = useActionFlowTokenAmount() + const txHash = useActionFlowTxHash() + + return ( + <> + {tokenAmount && ( - {/* TODO: Update styles */} + )} + {/* TODO: Update styles */} + {txHash && ( - - ), + )} + + ) +} + +function UnstakeContent() { + return ( + + You’ll receive your funds once the unstaking process is completed. Follow + the progress in your dashboard. + + ) +} + +const CONTENT: Record< + ActionFlowType, + { + heading: string + footer: string + renderComponent: () => ReactNode + } +> = { + [ACTION_FLOW_TYPES.STAKE]: { + heading: "Deposit received", + renderComponent: StakeContent, 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. - - ), + heading: "Withdrawal initiated", + renderComponent: UnstakeContent, footer: "The unstaking will continue in the background", }, } type SuccessModalProps = { type: ActionFlowType - tokenAmount: TokenAmount - txHash: string } -export default function SuccessModal({ - type, - tokenAmount, - txHash, -}: SuccessModalProps) { +export default function SuccessModal({ type }: SuccessModalProps) { const { closeModal, openModal } = useModal() const fetchDeposits = useFetchDeposits() const navigate = useNavigate() const allActivitiesCount = useAllActivitiesCount() - const { header, footer, renderBody } = CONTENT[type] + const { heading, footer, renderComponent } = CONTENT[type] const handleCloseModal = () => { closeModal() @@ -118,11 +128,11 @@ export default function SuccessModal({ return ( <> - {header} + {heading} - {renderBody(tokenAmount, txHash)} + {renderComponent()} diff --git a/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx b/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx new file mode 100644 index 000000000..11d7fd07a --- /dev/null +++ b/dapp/src/components/TransactionModal/TriggerTransactionModal.tsx @@ -0,0 +1,28 @@ +import React, { ReactNode } from "react" +import { ModalBody, ModalHeader, useTimeout } from "@chakra-ui/react" +import Spinner from "#/components/shared/Spinner" +import { TextMd } from "#/components/shared/Typography" +import { ONE_SEC_IN_MILLISECONDS } from "#/constants" + +export default function TriggerTransactionModal({ + callback, + children, + delay = ONE_SEC_IN_MILLISECONDS, +}: { + callback: () => void + children: ReactNode + delay?: number +}) { + useTimeout(callback, delay) + + return ( + <> + Waiting transaction... + + + Please complete the transaction in your wallet. + {children} + + + ) +} diff --git a/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx b/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx index ea3ad8aed..eafff1d21 100644 --- a/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx +++ b/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx @@ -9,12 +9,15 @@ export type TokenAmountFormValues = { [TOKEN_AMOUNT_FIELD_NAME]?: bigint } -export const useTokenAmountFormValue = () => { - const [, { value }] = useField< +export const useTokenAmountField = () => { + const [, { error, touched, value }] = useField< TokenAmountFormValues[typeof TOKEN_AMOUNT_FIELD_NAME] >(TOKEN_AMOUNT_FIELD_NAME) - return value + const hasError = !!error + const isValid = !hasError && touched && value + + return { value, isValid } } export type TokenAmountFormBaseProps = { diff --git a/dapp/src/components/shared/TokenBalanceInput/index.tsx b/dapp/src/components/shared/TokenBalanceInput/index.tsx index 3c198d369..eb7643498 100644 --- a/dapp/src/components/shared/TokenBalanceInput/index.tsx +++ b/dapp/src/components/shared/TokenBalanceInput/index.tsx @@ -125,8 +125,6 @@ export default function TokenBalanceInput({ valueRef.current = value ? userAmountToBigInt(value, decimals) : undefined } - const showConversionBalance = amount !== undefined && !!fiatCurrency - return ( @@ -172,10 +170,10 @@ export default function TokenBalanceInput({ errorMsgText={errorMsgText} hasError={hasError} /> - {!hasError && !helperText && showConversionBalance && ( + {!hasError && !helperText && !!fiatCurrency && ( diff --git a/dapp/src/components/shared/alerts/CardAlert.tsx b/dapp/src/components/shared/alerts/CardAlert.tsx index 1c40f8a4e..1a35fe36d 100644 --- a/dapp/src/components/shared/alerts/CardAlert.tsx +++ b/dapp/src/components/shared/alerts/CardAlert.tsx @@ -18,7 +18,8 @@ export function CardAlert({ { + dispatch(setStatus(PROCESS_STATUSES.PAUSED)) + } + + /** + * Function to resume the action flow process. + * It dispatches an action to set the status to "PENDING". + */ + const handleResume = () => { + dispatch(setStatus(PROCESS_STATUSES.PENDING)) + } + + return { handlePause, handleResume } +} diff --git a/dapp/src/hooks/useCurrencyConversion.ts b/dapp/src/hooks/useCurrencyConversion.ts index d16925b2e..11d8f787d 100644 --- a/dapp/src/hooks/useCurrencyConversion.ts +++ b/dapp/src/hooks/useCurrencyConversion.ts @@ -26,8 +26,9 @@ export function useCurrencyConversion({ const price = useAppSelector( isBtcUsdConversion(from, to) ? selectBtcUsdPrice : () => undefined, ) + const conversionAmount = useMemo(() => { - if (!from.amount || !price) return undefined + if (from.amount === undefined || !price) return undefined const { amount, currency } = from const { decimals, desiredDecimals } = CURRENCIES_BY_TYPE[currency] diff --git a/dapp/src/hooks/useTransactionDetails.ts b/dapp/src/hooks/useTransactionDetails.ts index f8f25f96b..687f4b665 100644 --- a/dapp/src/hooks/useTransactionDetails.ts +++ b/dapp/src/hooks/useTransactionDetails.ts @@ -1,30 +1,39 @@ import { useEffect, useState } from "react" +import { DepositFee } from "#/types" +import { initialDepositFee, useTransactionFee } from "./useTransactionFee" type UseTransactionDetailsResult = { - btcAmount: string - protocolFee: string - estimatedAmount: string + amount: bigint + transactionFee: DepositFee + estimatedAmount: bigint +} + +const initialTransactionDetails = { + amount: 0n, + transactionFee: initialDepositFee, + estimatedAmount: 0n, } export function useTransactionDetails(amount: bigint | undefined) { - const [details, setDetails] = useState< - UseTransactionDetailsResult | undefined - >(undefined) + // TODO: Temporary solution - Let's update when withdrawal fees are defined + const transactionFee = useTransactionFee(amount) + const [details, setDetails] = useState( + initialTransactionDetails, + ) useEffect(() => { if (!amount) { - setDetails(undefined) + setDetails(initialTransactionDetails) } else { - const protocolFee = amount / 10000n - const estimatedAmount = amount - protocolFee + const estimatedAmount = amount - transactionFee.total setDetails({ - btcAmount: amount.toString(), - protocolFee: protocolFee.toString(), - estimatedAmount: estimatedAmount.toString(), + amount, + transactionFee, + estimatedAmount, }) } - }, [amount]) + }, [amount, transactionFee]) return details } diff --git a/dapp/src/hooks/useTransactionFee.ts b/dapp/src/hooks/useTransactionFee.ts index b69ae971c..826b38a8b 100644 --- a/dapp/src/hooks/useTransactionFee.ts +++ b/dapp/src/hooks/useTransactionFee.ts @@ -4,7 +4,7 @@ import { useEffect, useState } from "react" import { DepositFee } from "#/types" import { useAppDispatch } from "./store" -const initialDepositFee = { +export const initialDepositFee = { tbtc: 0n, acre: 0n, total: 0n, diff --git a/dapp/src/pages/DashboardPage/DashboardCard.tsx b/dapp/src/pages/DashboardPage/DashboardCard.tsx index 555e10f4c..51a85a893 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -38,6 +38,7 @@ export default function DashboardCard(props: DashboardCardProps) { const { bitcoinAmount, positionPercentage, ...restProps } = props const openDepositModal = useTransactionModal(ACTION_FLOW_TYPES.STAKE) + const openWithdrawModal = useTransactionModal(ACTION_FLOW_TYPES.UNSTAKE) return ( @@ -90,7 +91,11 @@ export default function DashboardCard(props: DashboardCardProps) { - diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index 192a48f80..57f15cff7 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -15,6 +15,7 @@ export const buttonTheme: ComponentSingleStyleConfig = { fontSize: "md", py: 4, borderRadius: "lg", + h: 14, }, }, variants: { diff --git a/dapp/src/theme/Modal.ts b/dapp/src/theme/Modal.ts index 6bff3e642..523dbe420 100644 --- a/dapp/src/theme/Modal.ts +++ b/dapp/src/theme/Modal.ts @@ -73,8 +73,13 @@ const sizeXl = multiStyleConfig.definePartsStyle({ dialog: { maxW: "46.75rem" }, }) +const sizeLg = multiStyleConfig.definePartsStyle({ + dialog: { w: "30rem" }, +}) + const sizes = { xl: sizeXl, + lg: sizeLg, } export const modalTheme = multiStyleConfig.defineMultiStyleConfig({ diff --git a/dapp/src/theme/Spinner.ts b/dapp/src/theme/Spinner.ts index 678482d13..fd5aa47af 100644 --- a/dapp/src/theme/Spinner.ts +++ b/dapp/src/theme/Spinner.ts @@ -13,8 +13,14 @@ const sizeXl = defineStyle({ height: 16, }) +const size2Xl = defineStyle({ + width: 20, + height: 20, +}) + const sizes = { xl: sizeXl, + "2xl": size2Xl, } export const spinnerTheme = defineStyleConfig({ baseStyle, sizes }) diff --git a/dapp/src/types/action-flow.ts b/dapp/src/types/action-flow.ts index 6e870647f..20bcb50c7 100644 --- a/dapp/src/types/action-flow.ts +++ b/dapp/src/types/action-flow.ts @@ -22,6 +22,8 @@ export const PROCESS_STATUSES = { LOADING: "LOADING", FAILED: "FAILED", SUCCEEDED: "SUCCEEDED", + PAUSED: "PAUSED", + PENDING: "PENDING", } as const export type ProcessStatus = diff --git a/dapp/src/types/ledger-live-app.ts b/dapp/src/types/ledger-live-app.ts index c2e452ca0..c0fbf7bd3 100644 --- a/dapp/src/types/ledger-live-app.ts +++ b/dapp/src/types/ledger-live-app.ts @@ -8,3 +8,9 @@ export type UseRequestAccountReturn = { account: Account | null requestAccount: (...params: RequestAccountParams) => Promise } + +export type LedgerLiveError = { + message?: string + name?: string + stack?: string +} diff --git a/dapp/src/utils/index.ts b/dapp/src/utils/index.ts index 6df508ec4..89d130930 100644 --- a/dapp/src/utils/index.ts +++ b/dapp/src/utils/index.ts @@ -10,3 +10,4 @@ export * from "./exchangeApi" export * from "./verifyDepositAddress" export * from "./json" export * from "./activities" +export * from "./type-check" diff --git a/dapp/src/utils/numbers.ts b/dapp/src/utils/numbers.ts index a0a5c0baa..f9ca28b36 100644 --- a/dapp/src/utils/numbers.ts +++ b/dapp/src/utils/numbers.ts @@ -73,7 +73,7 @@ export const formatTokenAmount = ( } export const formatSatoshiAmount = ( - amount: number | string, + amount: number | string | bigint, desiredDecimals = 2, ) => formatTokenAmount(amount, 8, desiredDecimals) diff --git a/dapp/src/utils/type-check.ts b/dapp/src/utils/type-check.ts new file mode 100644 index 000000000..a8c5b4202 --- /dev/null +++ b/dapp/src/utils/type-check.ts @@ -0,0 +1,15 @@ +import { LedgerLiveError } from "#/types" + +export function isObject( + arg: unknown, +): arg is Record { + return typeof arg === "object" && arg !== null +} + +export function isString(arg: unknown): arg is string { + return typeof arg === "string" +} + +export function isLedgerLiveError(arg: unknown): arg is LedgerLiveError { + return isObject(arg) && arg.name === "Error" && isString(arg.message) +}