diff --git a/dapp/src/components/Layout.tsx b/dapp/src/components/Layout.tsx index 08c738b2b..43a0bc2d3 100644 --- a/dapp/src/components/Layout.tsx +++ b/dapp/src/components/Layout.tsx @@ -1,10 +1,10 @@ -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" import Sidebar from "./Sidebar" +import ModalRoot from "./ModalRoot" const wrapperVariants: Variants = { in: { opacity: 0, y: 48 }, @@ -39,6 +39,7 @@ function Layout() { + ) } 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..de804ca0d --- /dev/null +++ b/dapp/src/components/ModalRoot/withBaseModal.tsx @@ -0,0 +1,29 @@ +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/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index cc0ac9b67..3c1cbed37 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,13 +1,10 @@ import React, { ReactNode, 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, BaseFormProps } 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" @@ -30,8 +27,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) @@ -55,14 +52,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/ActiveFlowStep.tsx b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx index 0e27f2f74..bf8e5eafa 100644 --- a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx +++ b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useEffect } from "react" -import { useModalFlowContext } from "#/hooks" +import { useActionFlowActiveStep, useActionFlowType, useModal } from "#/hooks" import { ACTION_FLOW_STEPS_TYPES, ActionFlowType, @@ -18,14 +18,17 @@ const FLOW: Record ReactElement> = { } export function ActiveFlowStep() { - const { activeStep, type, onClose } = useModalFlowContext() + const { closeModal } = useModal() + const activeStep = useActionFlowActiveStep() + const type = useActionFlowType() + const numberOfSteps = Object.keys(ACTION_FLOW_STEPS_TYPES[type]).length useEffect(() => { if (activeStep > numberOfSteps) { - onClose() + closeModal() } - }, [activeStep, numberOfSteps, onClose]) + }, [activeStep, closeModal, numberOfSteps]) return FLOW[type](activeStep) } diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 1878b03c3..f3fb591e5 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,12 +1,12 @@ import React, { useCallback } from "react" import { + useActionFlowTokenAmount, + useAppDispatch, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, - useModalFlowContext, useStakeFlowContext, useToast, - useTransactionContext, useWalletContext, } from "#/hooks" import { logPromiseFailure } from "#/utils" @@ -17,27 +17,28 @@ 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 DELAY = ONE_SEC_IN_MILLISECONDS const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] export default function DepositBTCModal() { const { ethAccount } = useWalletContext() - const { tokenAmount } = useTransactionContext() - const { setStatus } = useModalFlowContext() + const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() + const dispatch = useAppDispatch() const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + () => dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)), + [dispatch], ) const onStakeBTCError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleStake = useExecuteFunction( stake, @@ -47,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, setStatus, handleStake]) + }, [closeToast, dispatch, handleStake]) const showError = useCallback(() => { openToast({ 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/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index 6ae4e1276..969a4e145 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -1,25 +1,26 @@ import React, { useCallback, useState } from "react" import { + useAppDispatch, useExecuteFunction, - useModalFlowContext, 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 dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) const [isServerError, setIsServerError] = useState(false) const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + () => 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 e46359396..3a8950325 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,21 +1,22 @@ import React, { useCallback } from "react" -import { useExecuteFunction, useModalFlowContext } 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" import { logPromiseFailure } from "#/utils" +import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { - const { setStatus } = useModalFlowContext() + const dispatch = useAppDispatch() const onSignMessageSuccess = useCallback(() => { - setStatus(PROCESS_STATUSES.SUCCEEDED) - }, [setStatus]) + dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) + }, [dispatch]) // TODO: After a failed attempt, we should display the message const onSignMessageError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleSignMessage = useExecuteFunction( // TODO: Use a correct function from the SDK @@ -25,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) - }, [setStatus, handleSignMessage]) + }, [dispatch, handleSignMessage]) return ( <> diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 776b36ec3..cfe99aea7 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,9 +1,10 @@ import React from "react" import { - useModalFlowContext, + useActionFlowStatus, + useActionFlowTokenAmount, + useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, - useTransactionContext, useWalletContext, } from "#/hooks" import { BitcoinIcon, EthereumIcon } from "#/assets/icons" @@ -23,8 +24,9 @@ export default function ModalContentWrapper({ const { btcAccount, ethAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() - const { type, status } = useModalFlowContext() - const { tokenAmount } = useTransactionContext() + const status = useActionFlowStatus() + const type = useActionFlowType() + const tokenAmount = useActionFlowTokenAmount() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) return ( diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 00811bbc5..0a2d84b6d 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -8,7 +8,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" @@ -66,7 +66,7 @@ type SuccessModalProps = { } export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() const { header, footer, renderBody } = CONTENT[type] @@ -80,7 +80,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 545417ef3..7fc1c88ad 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,80 +1,46 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react" -import { - ModalFlowContext, - ModalFlowContextValue, - StakeFlowProvider, - TransactionContextProvider, -} from "#/contexts" -import { useSidebar } from "#/hooks" -import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import React, { useEffect } from "react" +import { StakeFlowProvider } from "#/contexts" +import { useAppDispatch, useSidebar } from "#/hooks" +import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" -import ModalBase from "../shared/ModalBase" +import { resetState, setType } from "#/store/action-flow" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" - -const DEFAULT_ACTIVE_STEP = 1 +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() - - 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]) + const dispatch = useAppDispatch() useEffect(() => { - let timeout: NodeJS.Timeout + dispatch(setType(type)) + }, [dispatch, type]) - if (isOpen) { - openSideBar() - } else { - closeSidebar() - timeout = setTimeout(resetState, 100) + // eslint-disable-next-line arrow-body-style + useEffect(() => { + return () => { + dispatch(resetState()) } - return () => clearTimeout(timeout) - }, [isOpen, resetState, openSideBar, closeSidebar]) + }, [dispatch]) - const contextValue: ModalFlowContextValue = useMemo( - () => ({ - type, - activeStep, - status, - setStatus, - onClose, - goNext: handleGoNext, - }), - [type, activeStep, status, onClose, handleGoNext], - ) + useEffect(() => { + 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 87f3754c9..000000000 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ /dev/null @@ -1,15 +0,0 @@ -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< - ModalFlowContextValue | undefined ->(undefined) 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 47a8cd708..b4218417a 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,6 +3,4 @@ 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 5524c6f4b..b3405e7be 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -8,8 +8,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" export * from "./useTransactionHistoryTable" @@ -26,3 +24,4 @@ export * from "./useActivities" export * from "./useSize" export * from "./router" export * from "./useTransactionFee" +export * from "./useModal" diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index 2813057e1..396d4e68f 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -3,5 +3,9 @@ export * from "./useAppSelector" export * from "./useEstimatedBTCBalance" export * from "./useSharesBalance" 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/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/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/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/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/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/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) { - + diff --git a/dapp/src/pages/DashboardPage/PositionDetails.tsx b/dapp/src/pages/DashboardPage/PositionDetails.tsx index 179f46c6d..8a432b9f1 100644 --- a/dapp/src/pages/DashboardPage/PositionDetails.tsx +++ b/dapp/src/pages/DashboardPage/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/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 - diff --git a/dapp/src/store/action-flow/actionFlowSelectors.ts b/dapp/src/store/action-flow/actionFlowSelectors.ts new file mode 100644 index 000000000..a814d6290 --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSelectors.ts @@ -0,0 +1,15 @@ +import { ActionFlowType, ProcessStatus, TokenAmount } 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 + +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 new file mode 100644 index 000000000..a1fdf4c2f --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -0,0 +1,52 @@ +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 = { + type: "stake", + activeStep: 1, + status: PROCESS_STATUSES.IDLE, + tokenAmount: undefined, +} + +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 + }, + setTokenAmount(state, action: PayloadAction) { + state.tokenAmount = action.payload + }, + goNextStep(state) { + state.activeStep += 1 + }, + resetState(state) { + state.type = initialState.type + state.activeStep = initialState.activeStep + state.status = initialState.status + state.tokenAmount = initialState.tokenAmount + }, + }, +}) + +export const { setType, setStatus, setTokenAmount, 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/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 be4315b31..10b092a34 100644 --- a/dapp/src/store/reducer.ts +++ b/dapp/src/store/reducer.ts @@ -1,8 +1,12 @@ import { combineReducers } from "@reduxjs/toolkit" import { btcSlice } from "./btc/btcSlice" import { walletSlice } from "./wallet/walletSlice" +import { actionFlowSlice } from "./action-flow/actionFlowSlice" +import { modalSlice } from "./modal/modalSlice" export const reducer = combineReducers({ btc: btcSlice.reducer, wallet: walletSlice.reducer, + actionFlow: actionFlowSlice.reducer, + modal: modalSlice.reducer, }) diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index 0623cda8a..3e2a9719f 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -15,6 +15,7 @@ export * from "./size" export * from "./toast" export * from "./core" export * from "./fee" +export * from "./modal" export * from "./navigation" export * from "./subgraphAPI" export * from "./form" 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]