diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index c324d311341..0415b54a656 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -2819,7 +2819,11 @@ "otherYields": "Other %{symbol} Yields", "availableToDeposit": "Available to Deposit", "availableToDepositTooltip": "This is the amount of %{symbol} in your wallet that you can deposit into this yield opportunity.", - "getAsset": "Get %{symbol}" + "getAsset": "Get %{symbol}", + "potentialEarningsAmount": "%{amount}/yr at %{apy}% APY", + "depositNow": "Deposit Now", + "strategyInfo": "Strategy Info", + "overview": "Overview" }, "earn": { "enterFrom": "Enter from", diff --git a/src/components/MultiHopTrade/StandaloneMultiHopTrade.tsx b/src/components/MultiHopTrade/StandaloneMultiHopTrade.tsx index ef9664699e7..8af0115d6ba 100644 --- a/src/components/MultiHopTrade/StandaloneMultiHopTrade.tsx +++ b/src/components/MultiHopTrade/StandaloneMultiHopTrade.tsx @@ -25,7 +25,10 @@ import { import { tradeInput } from '@/state/slices/tradeInputSlice/tradeInputSlice' import { useAppDispatch, useAppSelector } from '@/state/store' -export type StandaloneTradeCardProps = TradeCardProps +export type StandaloneTradeCardProps = TradeCardProps & { + onSuccess?: () => void + isModal?: boolean +} const GetTradeRates = () => { useGetTradeRates() @@ -38,7 +41,9 @@ export const StandaloneMultiHopTrade = memo( defaultSellAssetId, isCompact, onChangeTab, + onSuccess, isStandalone, + isModal, }: StandaloneTradeCardProps) => { const dispatch = useAppDispatch() const location = useLocation() @@ -129,7 +134,9 @@ export const StandaloneMultiHopTrade = memo( ) }, @@ -138,11 +145,13 @@ export const StandaloneMultiHopTrade = memo( type StandaloneTradeRoutesProps = { isCompact?: boolean isStandalone?: boolean + isModal?: boolean onChangeTab: (newTab: TradeInputTab) => void + onSuccess?: () => void } const StandaloneTradeRoutes = memo( - ({ isCompact, isStandalone, onChangeTab }: StandaloneTradeRoutesProps) => { + ({ isCompact, isStandalone, isModal, onChangeTab, onSuccess }: StandaloneTradeRoutesProps) => { const location = useLocation() const tradeInputRef = useRef(null) @@ -165,7 +174,10 @@ const StandaloneTradeRoutes = memo( }, [location.pathname]) // Create memoized elements for each route - const tradeConfirmElement = useMemo(() => , [isCompact]) + const tradeConfirmElement = useMemo( + () => , + [isCompact, isModal, onSuccess], + ) const verifyAddressesElement = useMemo(() => , []) @@ -192,12 +204,13 @@ const StandaloneTradeRoutes = memo( () => ( ), - [isCompact, onChangeTab, isStandalone], + [isCompact, isModal, onChangeTab, isStandalone], ) return ( diff --git a/src/components/MultiHopTrade/components/SharedConfirm/SharedConfirm.tsx b/src/components/MultiHopTrade/components/SharedConfirm/SharedConfirm.tsx index 01d678d0397..7813f225b7c 100644 --- a/src/components/MultiHopTrade/components/SharedConfirm/SharedConfirm.tsx +++ b/src/components/MultiHopTrade/components/SharedConfirm/SharedConfirm.tsx @@ -2,7 +2,7 @@ import type { CardFooterProps } from '@chakra-ui/react' import { Card, CardBody, CardFooter, CardHeader, Heading } from '@chakra-ui/react' import type { JSX } from 'react' -import { cardstyles } from '../../const' +import { cardstyles, modalCardStyles } from '../../const' import { WithBackButton } from '../WithBackButton' import { TradeSlideTransition } from '@/components/MultiHopTrade/TradeSlideTransition' @@ -15,6 +15,7 @@ type SharedConfirmProps = { onBack: () => void headerTranslation: TextPropTypes['translation'] isLoading?: boolean + isModal?: boolean } const cardMinHeight = { base: 'calc(100vh - var(--mobile-nav-offset))', md: 'initial' } @@ -25,10 +26,17 @@ export const SharedConfirm = ({ footerContent, onBack, headerTranslation, + isModal, }: SharedConfirmProps) => { return ( - + diff --git a/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInput.tsx b/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInput.tsx index 352a1eb3302..de9057c3d55 100644 --- a/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInput.tsx +++ b/src/components/MultiHopTrade/components/SharedTradeInput/SharedTradeInput.tsx @@ -2,7 +2,7 @@ import type { CardProps } from '@chakra-ui/react' import { Box, Card, Center, Flex, useMediaQuery } from '@chakra-ui/react' import type { FormEvent, JSX } from 'react' -import { cardstyles } from '../../const' +import { cardstyles, modalCardStyles } from '../../const' import { SharedTradeInputHeader } from '../SharedTradeInput/SharedTradeInputHeader' import { useSharedWidth } from '../TradeInput/hooks/useSharedWidth' @@ -32,6 +32,7 @@ type SharedTradeInputProps = { onChangeTab: (newTab: TradeInputTab) => void onSubmit: (e: FormEvent) => void isStandalone?: boolean + isModal?: boolean } const cardBorderRadius = { base: '0', md: '2xl' } @@ -53,6 +54,7 @@ export const SharedTradeInput: React.FC = ({ onChangeTab, onSubmit, isStandalone, + isModal, }) => { const [isSmallerThanMd] = useMediaQuery(`(max-width: ${breakpoints.md})`, { ssr: false }) const [isSmallerThanXl] = useMediaQuery(`(max-width: ${breakpoints.xl})`, { ssr: false }) @@ -79,7 +81,7 @@ export const SharedTradeInput: React.FC = ({ borderRadius={cardBorderRadius} minHeight={cardMinHeight} height={!hasUserEnteredAmount && isSmallerThanMd ? cardMinHeight.base : 'initial'} - {...cardstyles} + {...(isModal ? modalCardStyles : cardstyles)} > { +type TradeConfirmProps = { + isCompact?: boolean + isModal?: boolean + onSuccess?: () => void +} + +export const TradeConfirm = ({ isCompact, isModal, onSuccess }: TradeConfirmProps) => { const navigate = useNavigate() const { isLoading } = useIsApprovalInitiallyNeeded() const dispatch = useAppDispatch() @@ -90,9 +96,18 @@ export const TradeConfirm = ({ isCompact }: { isCompact: boolean | undefined }) isCompact={isCompact} tradeQuoteStep={tradeQuoteStep} activeTradeId={activeTradeId} + onSwapTxBroadcast={onSuccess} /> ) - }, [isTradeComplete, activeQuote, tradeQuoteLastHop, tradeQuoteStep, activeTradeId, isCompact]) + }, [ + isTradeComplete, + activeQuote, + tradeQuoteLastHop, + tradeQuoteStep, + activeTradeId, + isCompact, + onSuccess, + ]) const isArbitrumBridgeWithdraw = useMemo(() => { return isArbitrumBridgeTradeQuoteOrRate(activeQuote) && activeQuote.direction === 'withdrawal' @@ -141,6 +156,7 @@ export const TradeConfirm = ({ isCompact }: { isCompact: boolean | undefined }) isLoading={isLoading} onBack={handleBack} headerTranslation={headerTranslation} + isModal={isModal} /> ) } diff --git a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirmFooter.tsx b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirmFooter.tsx index 3ac576301e0..b8717cab179 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirmFooter.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/TradeConfirmFooter.tsx @@ -51,11 +51,13 @@ type TradeConfirmFooterProps = { tradeQuoteStep: TradeQuoteStep activeTradeId: string isCompact: boolean | undefined + onSwapTxBroadcast?: () => void } export const TradeConfirmFooter: FC = ({ tradeQuoteStep, activeTradeId, + onSwapTxBroadcast, }) => { const [isExactAllowance, toggleIsExactAllowance] = useToggle(true) const translate = useTranslate() @@ -412,6 +414,7 @@ export const TradeConfirmFooter: FC = ({ activeTradeId={activeTradeId} isExactAllowance={isExactAllowance} isLoading={isNetworkFeeCryptoBaseUnitLoading || isNetworkFeeCryptoBaseUnitRefetching} + onSwapTxBroadcast={onSwapTxBroadcast} /> ) }, [ @@ -421,6 +424,7 @@ export const TradeConfirmFooter: FC = ({ isExactAllowance, isNetworkFeeCryptoBaseUnitLoading, isNetworkFeeCryptoBaseUnitRefetching, + onSwapTxBroadcast, ]) return ( diff --git a/src/components/MultiHopTrade/components/TradeConfirm/TradeFooterButton.tsx b/src/components/MultiHopTrade/components/TradeConfirm/TradeFooterButton.tsx index aeeed78ef01..44f854e94c7 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/TradeFooterButton.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/TradeFooterButton.tsx @@ -56,6 +56,7 @@ type TradeFooterButtonProps = { activeTradeId: string isExactAllowance: boolean isLoading?: boolean + onSwapTxBroadcast?: () => void } export const TradeFooterButton: FC = ({ @@ -64,6 +65,7 @@ export const TradeFooterButton: FC = ({ activeTradeId, isExactAllowance, isLoading = false, + onSwapTxBroadcast, }) => { const [isSubmitting, setIsSubmitting] = useState(false) const [shouldShowWarningAcknowledgement, setShouldShowWarningAcknowledgement] = useState(false) @@ -76,6 +78,7 @@ export const TradeFooterButton: FC = ({ currentHopIndex, activeTradeId, isExactAllowance, + onSwapTxBroadcast, }) const translate = useTranslate() const swapperName = useAppSelector(selectActiveSwapperName) diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx index 28071152f94..67177f64ffb 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeButtonProps.tsx @@ -31,6 +31,7 @@ type UseTradeButtonPropsProps = { currentHopIndex: SupportedTradeQuoteStepIndex activeTradeId: string isExactAllowance: boolean + onSwapTxBroadcast?: () => void } type TradeButtonProps = { @@ -45,6 +46,7 @@ export const useTradeButtonProps = ({ currentHopIndex, activeTradeId, isExactAllowance, + onSwapTxBroadcast, }: UseTradeButtonPropsProps): TradeButtonProps | undefined => { const dispatch = useAppDispatch() const navigate = useNavigate() @@ -143,7 +145,7 @@ export const useTradeButtonProps = ({ relayerTxHash, ]) - const executeTrade = useTradeExecution(currentHopIndex, activeTradeId) + const executeTrade = useTradeExecution(currentHopIndex, activeTradeId, onSwapTxBroadcast) const handleSignTx = useCallback(() => { if ( diff --git a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx index 6d6bda0d11e..1d13e537b61 100644 --- a/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx +++ b/src/components/MultiHopTrade/components/TradeConfirm/hooks/useTradeExecution.tsx @@ -67,6 +67,7 @@ import { store, useAppDispatch, useAppSelector } from '@/state/store' export const useTradeExecution = ( hopIndex: SupportedTradeQuoteStepIndex, confirmedTradeId: TradeQuote['id'], + onSwapTxBroadcast?: () => void, ) => { const translate = useTranslate() const dispatch = useAppDispatch() @@ -238,6 +239,8 @@ export const useTradeExecution = ( }) } + onSwapTxBroadcast?.() + // Don't navigate away during QuickBuy - let the QuickBuy component handle the success state if (!isQuickBuy) { navigate(TradeRoutePaths.Input) @@ -772,6 +775,7 @@ export const useTradeExecution = ( swapsById, toast, isQuickBuy, + onSwapTxBroadcast, ]) return executeTrade diff --git a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx index 939f09e303d..b6bcb71f232 100644 --- a/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx +++ b/src/components/MultiHopTrade/components/TradeInput/TradeInput.tsx @@ -86,12 +86,14 @@ type TradeInputProps = { tradeInputRef: React.MutableRefObject isCompact?: boolean isStandalone?: boolean + isModal?: boolean onChangeTab: (newTab: TradeInputTab) => void } export const TradeInput = ({ isCompact, isStandalone, + isModal, tradeInputRef, onChangeTab, }: TradeInputProps) => { @@ -609,6 +611,7 @@ export const TradeInput = ({ onSubmit={handleTradeQuoteConfirm} onChangeTab={onChangeTab} isStandalone={isStandalone} + isModal={isModal} /> ) diff --git a/src/components/MultiHopTrade/const.ts b/src/components/MultiHopTrade/const.ts index ed1689dd332..85aaa6d714a 100644 --- a/src/components/MultiHopTrade/const.ts +++ b/src/components/MultiHopTrade/const.ts @@ -27,3 +27,16 @@ export const cardstyles: CardProps = { boxShadow: '0 1px 0 rgba(255,255,255,0.05) inset, 0 2px 5px rgba(0,0,0,.2)', }, } + +export const modalCardStyles: CardProps = { + bg: 'transparent', + borderColor: 'transparent', + boxShadow: 'none', + borderWidth: 0, + borderRadius: 0, + _dark: { + bg: 'transparent', + borderColor: 'transparent', + boxShadow: 'none', + }, +} diff --git a/src/components/SwapperModal/SwapperModal.tsx b/src/components/SwapperModal/SwapperModal.tsx new file mode 100644 index 00000000000..6f6bacd0f82 --- /dev/null +++ b/src/components/SwapperModal/SwapperModal.tsx @@ -0,0 +1,76 @@ +import type { AssetId } from '@shapeshiftoss/caip' +import { memo, useCallback } from 'react' +import { MemoryRouter } from 'react-router-dom' + +import { SwapperModalContent } from './SwapperModalContent' + +import { Display } from '@/components/Display' +import { Dialog } from '@/components/Modal/components/Dialog' +import { DialogBody } from '@/components/Modal/components/DialogBody' +import { DialogCloseButton } from '@/components/Modal/components/DialogCloseButton' +import { DialogHeader } from '@/components/Modal/components/DialogHeader' +import { DialogTitle } from '@/components/Modal/components/DialogTitle' +import { TradeRoutePaths } from '@/components/MultiHopTrade/types' +import { tradeInput } from '@/state/slices/tradeInputSlice/tradeInputSlice' +import { useAppDispatch } from '@/state/store' + +type SwapperModalProps = { + isOpen: boolean + onClose: () => void + onSuccess?: () => void + defaultBuyAssetId?: AssetId + defaultSellAssetId?: AssetId +} + +const initialEntries = [ + { pathname: TradeRoutePaths.Input }, + { pathname: TradeRoutePaths.Confirm }, + { pathname: TradeRoutePaths.VerifyAddresses }, + { pathname: TradeRoutePaths.QuoteList }, +] + +export const SwapperModal = memo( + ({ isOpen, onClose, onSuccess, defaultBuyAssetId, defaultSellAssetId }: SwapperModalProps) => { + const dispatch = useAppDispatch() + + const handleClose = useCallback(() => { + dispatch(tradeInput.actions.clear()) + onClose() + }, [dispatch, onClose]) + + return ( + + + + {null} + + Trade + + + + + + + + {isOpen && ( + + + + )} + + + ) + }, +) diff --git a/src/components/SwapperModal/SwapperModalContent.tsx b/src/components/SwapperModal/SwapperModalContent.tsx new file mode 100644 index 00000000000..d307456906c --- /dev/null +++ b/src/components/SwapperModal/SwapperModalContent.tsx @@ -0,0 +1,69 @@ +import type { AssetId } from '@shapeshiftoss/caip' +import { memo, useCallback, useLayoutEffect, useRef } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { useNavigate } from 'react-router-dom' + +import { TradingErrorBoundary } from '@/components/ErrorBoundary' +import { StandaloneMultiHopTrade } from '@/components/MultiHopTrade/StandaloneMultiHopTrade' +import { TradeInputTab, TradeRoutePaths } from '@/components/MultiHopTrade/types' +import { selectAssetById } from '@/state/slices/assetsSlice/selectors' +import { tradeInput } from '@/state/slices/tradeInputSlice/tradeInputSlice' +import { useAppDispatch, useAppSelector } from '@/state/store' + +type SwapperModalContentProps = { + defaultBuyAssetId?: AssetId + defaultSellAssetId?: AssetId + onSuccess?: () => void +} + +export const SwapperModalContent = memo(function SwapperModalContent({ + defaultBuyAssetId, + defaultSellAssetId, + onSuccess, +}: SwapperModalContentProps) { + const methods = useForm({ mode: 'onChange' }) + const navigate = useNavigate() + const dispatch = useAppDispatch() + const hasInitialized = useRef(false) + + const defaultBuyAsset = useAppSelector(state => selectAssetById(state, defaultBuyAssetId ?? '')) + const defaultSellAsset = useAppSelector(state => selectAssetById(state, defaultSellAssetId ?? '')) + + useLayoutEffect(() => { + if (hasInitialized.current) return + hasInitialized.current = true + + dispatch(tradeInput.actions.clear()) + if (defaultBuyAsset) { + dispatch(tradeInput.actions.setBuyAsset(defaultBuyAsset)) + } + if (defaultSellAsset) { + dispatch(tradeInput.actions.setSellAsset(defaultSellAsset)) + } + }, [dispatch, defaultBuyAsset, defaultSellAsset]) + + const handleChangeTab = useCallback( + (newTab: TradeInputTab) => { + if (newTab === TradeInputTab.Trade) { + navigate(TradeRoutePaths.Input) + } + }, + [navigate], + ) + + return ( + + + + + + ) +}) diff --git a/src/pages/Yields/components/YieldAvailableToDeposit.tsx b/src/pages/Yields/components/YieldAvailableToDeposit.tsx index fe0b5675e47..8f0b5b60882 100644 --- a/src/pages/Yields/components/YieldAvailableToDeposit.tsx +++ b/src/pages/Yields/components/YieldAvailableToDeposit.tsx @@ -11,11 +11,11 @@ import { Tooltip, VStack, } from '@chakra-ui/react' -import { memo, useCallback, useMemo } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { useTranslate } from 'react-polyglot' import { Amount } from '@/components/Amount/Amount' -import { useTradeNavigation } from '@/components/MultiHopTrade/hooks/useTradeNavigation' +import { SwapperModal } from '@/components/SwapperModal/SwapperModal' import { KeyManager } from '@/context/WalletProvider/KeyManager' import { useFeatureFlag } from '@/hooks/useFeatureFlag/useFeatureFlag' import { useWallet } from '@/hooks/useWallet/useWallet' @@ -33,7 +33,7 @@ type YieldAvailableToDepositProps = { export const YieldAvailableToDeposit = memo( ({ yieldItem, inputTokenMarketData }: YieldAvailableToDepositProps) => { const translate = useTranslate() - const { navigateToTrade } = useTradeNavigation() + const [isSwapperModalOpen, setIsSwapperModalOpen] = useState(false) const { state: { isConnected }, } = useWallet() @@ -75,9 +75,8 @@ export const YieldAvailableToDeposit = memo( const hasAvailableBalance = availableBalance.gt(0) - const handleGetAsset = useCallback(() => { - navigateToTrade(inputTokenAssetId) - }, [navigateToTrade, inputTokenAssetId]) + const handleOpenSwapperModal = useCallback(() => setIsSwapperModalOpen(true), []) + const handleCloseSwapperModal = useCallback(() => setIsSwapperModalOpen(false), []) if (!inputTokenPrecision || !hasWallet) return null @@ -87,49 +86,57 @@ export const YieldAvailableToDeposit = memo( if (!hasAvailableBalance) { return ( - - - - - - - {translate('yieldXYZ.availableToDeposit')} - - - - - - - - - - - - - - - - - - - - + <> + + + + + + + {translate('yieldXYZ.availableToDeposit')} + + + + + + + + + + + + + + + + + + + + + + ) }