diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index b1fa2c6d5..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,8 +8,8 @@ import { useStakeFlowContext, useVerifyDepositAddress, } from "#/hooks" -import { logPromiseFailure } from "#/utils" -import { PROCESS_STATUSES } from "#/types" +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" @@ -20,6 +21,7 @@ export default function DepositBTCModal() { const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const verifyDepositAddress = useVerifyDepositAddress() const dispatch = useAppDispatch() + const { handlePause } = useActionFlowPause() const onStakeBTCSuccess = useCallback(() => { dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) @@ -42,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(() => showError(), [showError]) + const onDepositBTCError = useCallback( + (error: unknown) => { + if (!isLedgerLiveError(error)) return + + const isInterrupted = + error.message && error.message.includes("Signature interrupted by user") + if (isInterrupted) handlePause() + + showError(error) + }, + [showError, handlePause], + ) const { sendBitcoinTransaction, transactionHash } = useDepositBTCTransaction( onDepositBTCSuccess, diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index fa76d2b03..7d42dd72e 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -14,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, @@ -43,5 +44,7 @@ export default function ModalContentWrapper({ 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/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index 0bc778125..ce3b7762a 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -11,3 +11,4 @@ export * from "./useActionFlowTxHash" export * from "./useCompletedActivities" export * from "./useLatestActivities" export * from "./useAllActivitiesCount" +export * from "./useActionFlowPause" diff --git a/dapp/src/hooks/store/useActionFlowPause.ts b/dapp/src/hooks/store/useActionFlowPause.ts new file mode 100644 index 000000000..7bb907d88 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowPause.ts @@ -0,0 +1,29 @@ +import { setStatus } from "#/store/action-flow" +import { PROCESS_STATUSES } from "#/types" +import { useAppDispatch } from "./useAppDispatch" + +/** + * Custom hook that provides functions to pause and resume the action flow process. + * @returns An object containing the `handlePause` and `handleResume` functions. + */ +export function useActionFlowPause() { + const dispatch = useAppDispatch() + + /** + * Function to pause the action flow process. + * It dispatches an action to set the status to "PAUSED" if the current status is "PENDING". + */ + const handlePause = () => { + 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/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/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) +}