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/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/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/assets/images/right-sidebar-bg.png b/dapp/src/assets/images/right-sidebar-bg.png deleted file mode 100644 index 540134e58..000000000 Binary files a/dapp/src/assets/images/right-sidebar-bg.png and /dev/null differ 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/Sidebar.tsx b/dapp/src/components/Sidebar.tsx new file mode 100644 index 000000000..3dfc36915 --- /dev/null +++ b/dapp/src/components/Sidebar.tsx @@ -0,0 +1,81 @@ +import React from "react" +import { + Box, + Card, + CardBody, + Flex, + useMultiStyleConfig, + Image, +} from "@chakra-ui/react" +import { useSidebar, useDocsDrawer } from "#/hooks" +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" }, + { label: "FAQ", colorScheme: "gold" }, + { label: "Token Contract", colorScheme: "gold" }, + { label: "Bridge Contract", colorScheme: "gold" }, +] + +const BENEFITS = [ + { label: "1x Rewards Boost", iconSrc: rewardsBoostArrow }, + { label: "1x Mystery Box", iconSrc: mysteryBoxIcon }, + { label: "1x Season Key", iconSrc: seasonKeyIcon }, +] + +export default function Sidebar() { + const { isOpen } = useSidebar() + const { onOpen: openDocsDrawer } = useDocsDrawer() + const styles = useMultiStyleConfig("Sidebar") + + return ( + + + Rewards you’ll unlock + + + {BENEFITS.map(({ label, iconSrc }) => ( + + + {label} + + + + ))} + + + {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} - - ))} - - - ) -} diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 0f7c824c9..3c1cbed37 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,35 +1,39 @@ -import React, { useCallback, useState } from "react" -import { - ModalBody, - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, - ModalCloseButton, -} from "@chakra-ui/react" -import { - useModalFlowContext, - useStakeFlowContext, - useTransactionContext, - useWalletContext, -} from "#/hooks" -import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" +import React, { ReactNode, useCallback, useState } from "react" +import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" +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" -const TABS = Object.values(ACTION_FLOW_TYPES) +const FORM_DATA: Record< + ActionFlowType, + { + heading: string + renderComponent: (props: BaseFormProps) => ReactNode + } +> = { + stake: { + heading: "Deposit", + renderComponent: StakeFormModal, + }, + unstake: { + heading: "Withdraw", + renderComponent: 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 dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) + const { heading, renderComponent } = FORM_DATA[type] + const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address const ethAddress = ethAccount?.address @@ -48,14 +52,14 @@ function ActionFormModal({ defaultType }: { defaultType: 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( @@ -67,34 +71,13 @@ function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { return ( <> {!isLoading && } + {heading} - - - {TABS.map((actionFlowType) => ( - setType(actionFlowType)} - isDisabled={actionFlowType !== type && isLoading} - > - {actionFlowType} - - ))} - - - - - - - - - - + + {renderComponent({ + onSubmitForm: handleSubmitFormWrapper, + })} + ) 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 4b68afa54..f3fb591e5 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,43 +1,44 @@ -import React, { useCallback, useState } from "react" +import React, { useCallback } from "react" import { + useActionFlowTokenAmount, + useAppDispatch, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, - useModalFlowContext, useStakeFlowContext, useToast, - 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" +import { setStatus } from "#/store/action-flow" +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 [isLoading, setIsLoading] = useState(false) - const [buttonText, setButtonText] = useState("Deposit BTC") + 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,17 +48,16 @@ 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({ id: TOAST_ID, render: TOAST, }) - setButtonText("Try again") }, [openToast]) const onDepositBTCError = useCallback(() => showError(), [showError]) @@ -71,13 +71,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 +96,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. + + + + + ) } 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/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({ /> void -}) { +}: BaseFormProps) { const minDepositAmount = useMinDepositAmount() const { btcAccount } = useWalletContext() const tokenBalance = BigInt(btcAccount?.balance.toString() ?? "0") @@ -18,6 +17,7 @@ function StakeFormModal({ - Stake + Stake ) } 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..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, @@ -32,10 +32,10 @@ export default function ServerErrorModal({ return ( <> - + We're currently facing system issues. - + @@ -56,7 +56,6 @@ export default function ServerErrorModal({ 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/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..3a8950325 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,26 +1,22 @@ -import React, { useCallback, useEffect } from "react" -import { useExecuteFunction, useModalFlowContext } from "#/hooks" +import React, { useCallback } from "react" +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 { ReceiveSTBTCAlert } from "#/components/shared/alerts" +import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { - const { setStatus } = useModalFlowContext() - - useEffect(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, [setStatus]) + 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 @@ -30,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 ( <> @@ -46,7 +42,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/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 33c94bdbe..cfe99aea7 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,33 +1,32 @@ import React from "react" import { - useModalFlowContext, + useActionFlowStatus, + useActionFlowTokenAmount, + useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, - useTransactionContext, useWalletContext, } from "#/hooks" import { BitcoinIcon, EthereumIcon } from "#/assets/icons" -import { ActionFlowType, PROCESS_STATUSES } from "#/types" +import { PROCESS_STATUSES } from "#/types" import { isSupportedBTCAddressType } from "#/utils" import ActionFormModal from "./ActionFormModal" import ErrorModal from "./ErrorModal" import LoadingModal from "./LoadingModal" import MissingAccountModal from "./MissingAccountModal" -import ResumeModal from "./ResumeModal" import SuccessModal from "./SuccessModal" export default function ModalContentWrapper({ - defaultType, children, }: { - defaultType: ActionFlowType children: React.ReactNode }) { const { btcAccount, ethAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() - const { type, status, onClose, onResume } = useModalFlowContext() - const { tokenAmount } = useTransactionContext() + const status = useActionFlowStatus() + const type = useActionFlowType() + const tokenAmount = useActionFlowTokenAmount() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) return ( @@ -47,10 +46,7 @@ export default function ModalContentWrapper({ /> ) - if (!tokenAmount) return - - if (status === PROCESS_STATUSES.PAUSED) - return + if (!tokenAmount) return if (status === PROCESS_STATUSES.LOADING) return diff --git a/dapp/src/components/TransactionModal/ResumeModal.tsx b/dapp/src/components/TransactionModal/ResumeModal.tsx deleted file mode 100644 index 9e5bf6bd8..000000000 --- a/dapp/src/components/TransactionModal/ResumeModal.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useEffect } 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 { useToast } from "#/hooks" - -export default function ResumeModal({ - onResume, - onClose, -}: { - onResume: () => void - onClose: () => void -}) { - const { closeAll } = useToast() - - useEffect(() => { - // All notifications should be closed when the user is in the resume modal. - closeAll() - }, [closeAll]) - - 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 e6dac590e..0a2d84b6d 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -1,21 +1,63 @@ import React from "react" import { - Box, Button, + HStack, ModalBody, ModalFooter, ModalHeader, 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 { 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 = { @@ -24,34 +66,27 @@ type SuccessModalProps = { } export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() + + const { header, footer, renderBody } = CONTENT[type] return ( <> - {HEADER[type]} + {header} - - - + {renderBody(tokenAmount)} - - - + + + {footer} + ) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 7d20c8e48..7fc1c88ad 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,109 +1,46 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react" -import { - ModalFlowContext, - ModalFlowContextValue, - StakeFlowProvider, - TransactionContextProvider, -} from "#/contexts" -import { useSidebar } from "#/hooks" -import { - ACTION_FLOW_TYPES, - 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 = { - isOpen: boolean - defaultType?: ActionFlowType - onClose: () => void -} + type: ActionFlowType +} & BaseModalProps -export default function TransactionModal({ - isOpen, - defaultType = ACTION_FLOW_TYPES.STAKE, - onClose, -}: TransactionModalProps) { +function TransactionModalBase({ type }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() - - const [type, setType] = useState(defaultType) - const [activeStep, setActiveStep] = useState(DEFAULT_ACTIVE_STEP) - const [status, setStatus] = useState(PROCESS_STATUSES.IDLE) - - const handleResume = useCallback(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, []) - - const handleGoNext = useCallback(() => { - setActiveStep((prevStep) => prevStep + 1) - }, []) - - const handleClose = useCallback(() => { - if (status === PROCESS_STATUSES.PENDING) { - setStatus(PROCESS_STATUSES.PAUSED) - } else { - onClose() - } - }, [onClose, status]) - - const resetState = useCallback(() => { - setType(defaultType) - setActiveStep(DEFAULT_ACTIVE_STEP) - setStatus(PROCESS_STATUSES.IDLE) - }, [defaultType, setStatus]) + const dispatch = useAppDispatch() useEffect(() => { - setType(defaultType) - }, [defaultType]) + dispatch(setType(type)) + }, [dispatch, type]) + // eslint-disable-next-line arrow-body-style useEffect(() => { - let timeout: NodeJS.Timeout - - if (isOpen) { - openSideBar() - } else { - closeSidebar() - timeout = setTimeout(resetState, 100) + return () => { + dispatch(resetState()) } - return () => clearTimeout(timeout) - }, [isOpen, resetState, openSideBar, closeSidebar]) + }, [dispatch]) - const contextValue: ModalFlowContextValue = useMemo( - () => ({ - type, - activeStep, - status, - setType, - setStatus, - onClose: handleClose, - onResume: handleResume, - goNext: handleGoNext, - }), - [type, activeStep, status, handleClose, handleResume, handleGoNext], - ) + useEffect(() => { + openSideBar() + return () => closeSidebar() + }, [closeSidebar, openSideBar]) return ( - - - - - - - - - - - - + + + + + + ) } + +const TransactionModal = withBaseModal(TransactionModalBase) +export default TransactionModal 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/components/shared/FeesDetails/FeesItem.tsx b/dapp/src/components/shared/FeesDetails/FeesItem.tsx index 6697e7d6d..5917dd76e 100644 --- a/dapp/src/components/shared/FeesDetails/FeesItem.tsx +++ b/dapp/src/components/shared/FeesDetails/FeesItem.tsx @@ -6,22 +6,16 @@ import { CurrencyBalanceWithConversion } from "../CurrencyBalanceWithConversion" type FeesDetailsItemAmountItemProps = ComponentProps< typeof CurrencyBalanceWithConversion > & - Pick + Pick function FeesDetailsAmountItem({ label, - sublabel, tooltip, from, to, }: FeesDetailsItemAmountItemProps) { return ( - + - - {children} - - ) -} diff --git a/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx b/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx index 55a1151f8..ea3ad8aed 100644 --- a/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx +++ b/dapp/src/components/shared/TokenAmountForm/TokenAmountFormBase.tsx @@ -22,6 +22,7 @@ export type TokenAmountFormBaseProps = { tokenBalance: bigint tokenBalanceInputPlaceholder: string currency: CurrencyType + fiatCurrency?: CurrencyType children?: React.ReactNode } @@ -29,6 +30,7 @@ export default function TokenAmountFormBase({ formId, tokenBalance, currency, + fiatCurrency, tokenBalanceInputPlaceholder, children, ...formikProps @@ -40,6 +42,7 @@ export default function TokenAmountFormBase({ tokenBalance={tokenBalance} placeholder={tokenBalanceInputPlaceholder} currency={currency} + fiatCurrency={fiatCurrency} /> {children} 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/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 && ( diff --git a/dapp/src/components/shared/alerts/ReceiveSTBTCAlert.tsx b/dapp/src/components/shared/alerts/ReceiveSTBTCAlert.tsx deleted file mode 100644 index d873f70b3..000000000 --- a/dapp/src/components/shared/alerts/ReceiveSTBTCAlert.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react" -import { Highlight } from "@chakra-ui/react" -import { CardAlert, CardAlertProps } from "./CardAlert" -import { TextMd } from "../Typography" - -export function ReceiveSTBTCAlert({ ...restProps }: CardAlertProps) { - return ( - // TODO: Add the correct action after click - - - - You will receive stBTC liquid staking token at this Ethereum address - once the staking transaction is completed. - - - - ) -} diff --git a/dapp/src/components/shared/alerts/index.ts b/dapp/src/components/shared/alerts/index.ts index 79555f74d..50fccc8b9 100644 --- a/dapp/src/components/shared/alerts/index.ts +++ b/dapp/src/components/shared/alerts/index.ts @@ -1,3 +1,2 @@ export * from "./Alert" export * from "./CardAlert" -export * from "./ReceiveSTBTCAlert" diff --git a/dapp/src/contexts/ModalFlowContext.tsx b/dapp/src/contexts/ModalFlowContext.tsx deleted file mode 100644 index 82b9c6447..000000000 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ActionFlowType, ProcessStatus } from "#/types" -import { createContext } from "react" - -export type ModalFlowContextValue = { - type: ActionFlowType - activeStep: number - status: ProcessStatus - setType: React.Dispatch> - onClose: () => void - onResume: () => 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 6643755f1..6edbbc898 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,7 +3,5 @@ export * from "./WalletApiReactTransportProvider" export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" -export * from "./ModalFlowContext" -export * from "./TransactionContext" export * from "./StakeFlowContext" export * from "./PaginationContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 4e5aca669..f4a1adf25 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" @@ -27,3 +25,4 @@ export * from "./useSize" export * from "./router" export * from "./useTransactionFee" export * from "./usePagination" +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/useInitApp.ts b/dapp/src/hooks/useInitApp.ts index c8e1a4a23..bfae3073c 100644 --- a/dapp/src/hooks/useInitApp.ts +++ b/dapp/src/hooks/useInitApp.ts @@ -1,7 +1,6 @@ import { useInitDataFromSdk, useInitializeAcreSdk } from "./sdk" import { useSentry } from "./sentry" import { useFetchBTCPriceUSD } from "./useFetchBTCPriceUSD" -import { useInitGlobalToasts } from "./toasts/useInitGlobalToasts" import { useInitDataFromSubgraph } from "./subgraph" export function useInitApp() { @@ -10,7 +9,8 @@ export function useInitApp() { useSentry() useInitializeAcreSdk() useFetchBTCPriceUSD() - useInitGlobalToasts() + // Let's hide this logic and remove it when we no longer need it. + // useInitGlobalToasts() useInitDataFromSdk() useInitDataFromSubgraph() 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 53bd4b16d..d1511dfba 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -15,7 +15,8 @@ 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 { useModal } from "#/hooks" const buttonStyles: ButtonProps = { size: "lg", @@ -34,6 +35,9 @@ type DashboardCardProps = CardProps & { export default function DashboardCard(props: DashboardCardProps) { const { bitcoinAmount, positionPercentage, ...restProps } = props + + const { openModal } = useModal() + return ( @@ -82,7 +86,12 @@ export default function DashboardCard(props: DashboardCardProps) { - + 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 + {children} + + ) +} + +export default PageLayout diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx new file mode 100644 index 000000000..78d26a4d4 --- /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/index.ts b/dapp/src/pages/DashboardPage/PageLayout/index.ts new file mode 100644 index 000000000..8197d5e88 --- /dev/null +++ b/dapp/src/pages/DashboardPage/PageLayout/index.ts @@ -0,0 +1,2 @@ +export { default as PageLayout } from "./PageLayout" +export { default as PageLayoutColumn } from "./PageLayoutColumn" diff --git a/dapp/src/pages/DashboardPage/PositionDetails.tsx b/dapp/src/pages/DashboardPage/PositionDetails.tsx index db6eb4387..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,23 +42,18 @@ export default function PositionDetails(props: CardProps) { - ) } diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index ae1e36102..f9dff67ac 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,42 +1,48 @@ 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 { useEstimatedBTCBalance } 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 bitcoinWalletBalance = useEstimatedBTCBalance() + return ( - - - {/* TODO: Handle click actions */} - - Show values in {USD.symbol} - + + + + + + + + + - - - - - - - - - - + + + + Coming soon... + + Gamification placeholder + + ) } 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/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) 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", 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..3e2a9719f 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -15,5 +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]