Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
d265d3b
fix: improve yield UI terminology, button states, and add Get Asset f…
gomesalexandre Jan 19, 2026
178f053
Merge origin/develop into feat_yield_final_polish
gomesalexandre Jan 19, 2026
8a55116
feat(yields): add "Get Asset" button to redirect users to trade page
gomesalexandre Jan 19, 2026
8768bc8
feat(yields): add SwapperModal for in-context asset acquisition
gomesalexandre Jan 19, 2026
b07d740
Merge remote-tracking branch 'origin/develop' into feat_yield_final_p…
gomesalexandre Jan 19, 2026
578950d
chore: remove diff files
gomesalexandre Jan 19, 2026
dcceb87
fix: show My Position card with Connect Wallet when no wallet connected
gomesalexandre Jan 19, 2026
cd8661d
fix: add missing yieldXYZ.getAsset translation
gomesalexandre Jan 19, 2026
b1c7bf2
docs: add i18n workflow note to CLAUDE.md
gomesalexandre Jan 19, 2026
9d13a59
fix: use useTradeNavigation for Get Asset button to properly set buy …
gomesalexandre Jan 19, 2026
8ac3cbd
Merge remote-tracking branch 'origin/feat_yield_final_polish' into fe…
gomesalexandre Jan 19, 2026
bd8e0d9
fix: simplify SwapperModal - remove header on desktop, keep on mobile
gomesalexandre Jan 19, 2026
ce56a1b
fix: use isCompact mode for SwapperModal to show single-column view
gomesalexandre Jan 19, 2026
3523ced
chore: remove unnecessary barrel file
gomesalexandre Jan 19, 2026
b7d68cb
chore: resolve merge conflicts from develop squash merge
gomesalexandre Jan 20, 2026
1775843
feat(swapper-modal): add onSuccess callback for auto-close on trade c…
gomesalexandre Jan 20, 2026
6443150
feat(swapper-modal): add modal styling and fire callback on swap broa…
gomesalexandre Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
- Add English copy to `src/assets/translations/en/main.json` (find appropriate section)
- Ignore other language translation files - only update English
- Use the translation hook: `useTranslate()` from `react-polyglot`
- **Both steps required**: Translations must be (1) added to `en/main.json` AND (2) consumed via `translate('key')` - missing either step results in untranslated strings showing raw keys

### Feature Flags
- Feature flags are stored in Redux state under `preferences.featureFlags`
Expand Down
1 change: 1 addition & 0 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -2830,6 +2830,7 @@
"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}",
"potentialEarningsAmount": "%{amount}/yr at %{apy}% APY",
"depositNow": "Deposit Now",
"strategyInfo": "Strategy Info",
Expand Down
74 changes: 74 additions & 0 deletions src/components/SwapperModal/SwapperModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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
defaultBuyAssetId?: AssetId
defaultSellAssetId?: AssetId
}

const initialEntries = [
{ pathname: TradeRoutePaths.Input },
{ pathname: TradeRoutePaths.Confirm },
{ pathname: TradeRoutePaths.VerifyAddresses },
{ pathname: TradeRoutePaths.QuoteList },
]

export const SwapperModal = memo(
({ isOpen, onClose, defaultBuyAssetId, defaultSellAssetId }: SwapperModalProps) => {
const dispatch = useAppDispatch()

const handleClose = useCallback(() => {
dispatch(tradeInput.actions.clear())
onClose()
}, [dispatch, onClose])

return (
<Dialog
isOpen={isOpen}
onClose={handleClose}
height='auto'
modalProps={{
closeOnOverlayClick: true,
size: { base: 'full', md: 'md' },
}}
>
<Display.Mobile>
<DialogHeader>
<DialogHeader.Left>{null}</DialogHeader.Left>
<DialogHeader.Middle>
<DialogTitle>Trade</DialogTitle>
</DialogHeader.Middle>
<DialogHeader.Right>
<DialogCloseButton />
</DialogHeader.Right>
</DialogHeader>
</Display.Mobile>
<DialogBody py={0} px={{ base: 4, md: 0 }} flex={1} display='flex' flexDirection='column'>
{isOpen && (
<MemoryRouter initialEntries={initialEntries} initialIndex={0}>
<SwapperModalContent
defaultBuyAssetId={defaultBuyAssetId}
defaultSellAssetId={defaultSellAssetId}
/>
</MemoryRouter>
)}
</DialogBody>
</Dialog>
)
},
)
65 changes: 65 additions & 0 deletions src/components/SwapperModal/SwapperModalContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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
}

export const SwapperModalContent = memo(function SwapperModalContent({
defaultBuyAssetId,
defaultSellAssetId,
}: 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 (
<FormProvider {...methods}>
<TradingErrorBoundary>
<StandaloneMultiHopTrade
defaultBuyAssetId={defaultBuyAssetId}
defaultSellAssetId={defaultSellAssetId}
onChangeTab={handleChangeTab}
isCompact
isStandalone
/>
</TradingErrorBoundary>
</FormProvider>
)
})
93 changes: 89 additions & 4 deletions src/pages/Yields/components/YieldAvailableToDeposit.tsx
Comment thread
gomesalexandre marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { InfoOutlineIcon } from '@chakra-ui/icons'
import { Box, Card, CardBody, Flex, Heading, HStack, Text, Tooltip, VStack } from '@chakra-ui/react'
import { memo, useMemo } from 'react'
import {
Box,
Button,
Card,
CardBody,
Flex,
Heading,
HStack,
Text,
Tooltip,
VStack,
} from '@chakra-ui/react'
import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'

import { Amount } from '@/components/Amount/Amount'
import { SwapperModal } from '@/components/SwapperModal/SwapperModal'
import { KeyManager } from '@/context/WalletProvider/KeyManager'
import { useFeatureFlag } from '@/hooks/useFeatureFlag/useFeatureFlag'
import { useWallet } from '@/hooks/useWallet/useWallet'
import { bnOrZero } from '@/lib/bignumber/bignumber'
import type { AugmentedYieldDto } from '@/lib/yieldxyz/types'
import { selectWalletType } from '@/state/slices/localWalletSlice/selectors'
import { selectPortfolioCryptoBalanceBaseUnitByFilter } from '@/state/slices/selectors'
import { useAppSelector } from '@/state/store'

Expand All @@ -17,6 +33,19 @@ type YieldAvailableToDepositProps = {
export const YieldAvailableToDeposit = memo(
({ yieldItem, inputTokenMarketData }: YieldAvailableToDepositProps) => {
const translate = useTranslate()
const [isSwapperModalOpen, setIsSwapperModalOpen] = useState(false)
const {
state: { isConnected },
} = useWallet()
const isLedgerReadOnlyEnabled = useFeatureFlag('LedgerReadOnly')
const walletType = useAppSelector(selectWalletType)
const isLedgerReadOnly = isLedgerReadOnlyEnabled && walletType === KeyManager.Ledger

// Either wallet is physically connected, or it's a Ledger in read-only mode
const hasWallet = useMemo(
() => isConnected || isLedgerReadOnly,
[isConnected, isLedgerReadOnly],
)

const inputToken = yieldItem.inputTokens[0]
const inputTokenAssetId = inputToken?.assetId ?? ''
Expand Down Expand Up @@ -46,13 +75,69 @@ export const YieldAvailableToDeposit = memo(

const hasAvailableBalance = availableBalance.gt(0)

if (!inputTokenPrecision) return null
const handleOpenSwapperModal = useCallback(() => setIsSwapperModalOpen(true), [])
const handleCloseSwapperModal = useCallback(() => setIsSwapperModalOpen(false), [])

if (!inputTokenPrecision || !hasWallet) return null

const tooltipLabel = translate('yieldXYZ.availableToDepositTooltip', {
symbol: yieldItem.token.symbol,
})

if (!hasAvailableBalance) return null
if (!hasAvailableBalance) {
return (
<>
<Card variant='dashboard'>
<CardBody p={{ base: 4, md: 5 }}>
<VStack spacing={4} align='stretch'>
<Flex justifyContent='space-between' alignItems='center'>
<HStack spacing={2}>
<Heading
as='h3'
size='sm'
textTransform='uppercase'
color='text.subtle'
letterSpacing='wider'
>
{translate('yieldXYZ.availableToDeposit')}
</Heading>
<Tooltip label={tooltipLabel} placement='top'>
<InfoOutlineIcon color='text.subtle' boxSize={3} cursor='help' />
</Tooltip>
</HStack>
</Flex>

<Box>
<Text fontSize='2xl' fontWeight='800' lineHeight='1'>
<Amount.Fiat value='0' />
</Text>
<Text fontSize='sm' color='text.subtle' mt={1}>
<Amount.Crypto value='0' symbol={yieldItem.token.symbol} abbreviated />
</Text>
</Box>

<Button
colorScheme='blue'
size='lg'
height={12}
borderRadius='xl'
onClick={handleOpenSwapperModal}
width='full'
fontWeight='bold'
>
{translate('yieldXYZ.getAsset', { symbol: yieldItem.token.symbol })}
</Button>
</VStack>
</CardBody>
</Card>
<SwapperModal
isOpen={isSwapperModalOpen}
onClose={handleCloseSwapperModal}
defaultBuyAssetId={inputTokenAssetId}
/>
</>
)
}

return (
<Card variant='dashboard'>
Expand Down
52 changes: 51 additions & 1 deletion src/pages/Yields/components/YieldPositionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import { useNavigate } from 'react-router-dom'

import { Amount } from '@/components/Amount/Amount'
import { Display } from '@/components/Display'
import { WalletActions } from '@/context/WalletProvider/actions'
import { useBrowserRouter } from '@/hooks/useBrowserRouter/useBrowserRouter'
import { useWallet } from '@/hooks/useWallet/useWallet'
import { bnOrZero } from '@/lib/bignumber/bignumber'
import type { AugmentedYieldDto } from '@/lib/yieldxyz/types'
import { YieldBalanceType } from '@/lib/yieldxyz/types'
Expand Down Expand Up @@ -67,6 +69,12 @@ export const YieldPositionCard = memo(
const translate = useTranslate()
const navigate = useNavigate()
const { location } = useBrowserRouter()
const { dispatch: walletDispatch } = useWallet()

const handleConnectWallet = useCallback(
() => walletDispatch({ type: WalletActions.SET_WALLET_MODAL, payload: true }),
[walletDispatch],
)

const { chainId } = yieldItem
const { accountId: contextAccountId, accountNumber } = useYieldAccount()
Expand Down Expand Up @@ -360,7 +368,49 @@ export const YieldPositionCard = memo(
claimableSection,
])

if (!accountId) return null
if (!accountId) {
return (
<Card variant='dashboard'>
<CardBody p={{ base: 4, md: 5 }}>
<Flex justifyContent='space-between' alignItems='center' mb={4}>
<Heading
as='h3'
size='sm'
textTransform='uppercase'
color='text.subtle'
letterSpacing='wider'
>
{translate('yieldXYZ.myPosition')}
</Heading>
</Flex>
<VStack spacing={4} align='stretch'>
<Box>
<Text fontSize='xs' color='text.subtle' mb={1} textTransform='uppercase'>
{translate('yieldXYZ.totalValue')}
</Text>
<Text fontSize='3xl' fontWeight='800' lineHeight='1'>
<Amount.Fiat value='0' />
</Text>
<Text fontSize='sm' color='text.subtle' mt={1}>
<Amount.Crypto value='0' symbol={yieldItem.token.symbol} abbreviated />
</Text>
</Box>
<Button
colorScheme='blue'
size='lg'
height={12}
borderRadius='xl'
onClick={handleConnectWallet}
width='full'
fontWeight='bold'
>
{translate('common.connectWallet')}
</Button>
</VStack>
</CardBody>
</Card>
)
}

if (isBalancesLoading) {
return (
Expand Down
7 changes: 6 additions & 1 deletion src/pages/Yields/components/YieldSuccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ type YieldSuccessProps = {
accountId?: AccountId
onDone: () => void
showConfetti?: boolean
successMessageKey?: 'successEnter' | 'successExit' | 'successClaim'
successMessageKey?:
| 'successEnter'
| 'successExit'
| 'successClaim'
| 'successUnstaked'
| 'successWithdrawn'
}

export const YieldSuccess = memo(
Expand Down