From c0661573391f39266ca7a5e2d5c411fbc1f96cc8 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:03:23 -1000 Subject: [PATCH 1/2] fix: show action-relevant info in exit/withdraw modal Hide APY and estimated yearly earnings when withdrawing since they're irrelevant for exit actions. Filter yield explainers by action relevance so exit modal only shows withdraw/unbonding info, not deposit info. Co-Authored-By: Claude Opus 4.5 --- .../components/Earn/EarnConfirm.tsx | 2 +- .../Yields/components/YieldEnterModal.tsx | 1 + .../Yields/components/YieldExplainers.tsx | 112 +++++++++++------- src/pages/Yields/components/YieldForm.tsx | 41 ++++--- 4 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx index e3328675b08..8af2c6e1f60 100644 --- a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx +++ b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx @@ -367,7 +367,7 @@ export const EarnConfirm = memo(() => { {selectedYield && ( - + )} diff --git a/src/pages/Yields/components/YieldEnterModal.tsx b/src/pages/Yields/components/YieldEnterModal.tsx index 11b492c8acd..c52d39b7419 100644 --- a/src/pages/Yields/components/YieldEnterModal.tsx +++ b/src/pages/Yields/components/YieldEnterModal.tsx @@ -665,6 +665,7 @@ export const YieldEnterModal = memo( {stepsToShow.length > 0 && } diff --git a/src/pages/Yields/components/YieldExplainers.tsx b/src/pages/Yields/components/YieldExplainers.tsx index d1657b526bf..0777dfae5a5 100644 --- a/src/pages/Yields/components/YieldExplainers.tsx +++ b/src/pages/Yields/components/YieldExplainers.tsx @@ -15,6 +15,7 @@ const infoIcon = type ExplainerItem = { icon: ReactNode textKey: string + relevance: 'enter' | 'exit' | 'both' } const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] => { @@ -29,31 +30,40 @@ const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] = textKey: outputTokenSymbol ? 'earn.explainers.liquidStakingReceive' : 'earn.explainers.liquidStakingTrade', + relevance: 'enter' as const, + }, + { icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule', relevance: 'enter' as const }, + { + icon: infoIcon, + textKey: 'earn.explainers.liquidStakingWithdraw', + relevance: 'both' as const, }, - { icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule' }, - { icon: infoIcon, textKey: 'earn.explainers.liquidStakingWithdraw' }, ] case 'native-staking': case 'pooled-staking': case 'staking': return [ - { icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule' }, - { icon: infoIcon, textKey: 'earn.explainers.stakingUnbonding' }, + { icon: giftIcon, textKey: 'earn.explainers.rewardsSchedule', relevance: 'enter' as const }, + { icon: infoIcon, textKey: 'earn.explainers.stakingUnbonding', relevance: 'both' as const }, ] case 'restaking': return [ - { icon: giftIcon, textKey: 'earn.explainers.restakingYield' }, - { icon: infoIcon, textKey: 'earn.explainers.restakingWithdraw' }, + { icon: giftIcon, textKey: 'earn.explainers.restakingYield', relevance: 'enter' as const }, + { + icon: infoIcon, + textKey: 'earn.explainers.restakingWithdraw', + relevance: 'both' as const, + }, ] case 'vault': return [ - { icon: giftIcon, textKey: 'earn.explainers.vaultYield' }, - { icon: infoIcon, textKey: 'earn.explainers.vaultWithdraw' }, + { icon: giftIcon, textKey: 'earn.explainers.vaultYield', relevance: 'enter' as const }, + { icon: infoIcon, textKey: 'earn.explainers.vaultWithdraw', relevance: 'both' as const }, ] case 'lending': return [ - { icon: giftIcon, textKey: 'earn.explainers.lendingYield' }, - { icon: infoIcon, textKey: 'earn.explainers.lendingWithdraw' }, + { icon: giftIcon, textKey: 'earn.explainers.lendingYield', relevance: 'enter' as const }, + { icon: infoIcon, textKey: 'earn.explainers.lendingWithdraw', relevance: 'both' as const }, ] default: return [] @@ -63,48 +73,58 @@ const getYieldExplainers = (selectedYield: AugmentedYieldDto): ExplainerItem[] = type YieldExplainersProps = { selectedYield: AugmentedYieldDto sellAssetSymbol?: string + action: 'enter' | 'exit' | 'claim' } -export const YieldExplainers = memo(({ selectedYield, sellAssetSymbol }: YieldExplainersProps) => { - const translate = useTranslate() +export const YieldExplainers = memo( + ({ selectedYield, sellAssetSymbol, action }: YieldExplainersProps) => { + const translate = useTranslate() - const explainers = useMemo(() => getYieldExplainers(selectedYield), [selectedYield]) + const actionRelevance = action === 'enter' ? 'enter' : 'exit' + const explainers = useMemo( + () => + getYieldExplainers(selectedYield).filter( + e => e.relevance === actionRelevance || e.relevance === 'both', + ), + [selectedYield, actionRelevance], + ) - const rewardSchedule = selectedYield.mechanics.rewardSchedule - const outputSymbol = selectedYield.outputToken?.symbol + const rewardSchedule = selectedYield.mechanics.rewardSchedule + const outputSymbol = selectedYield.outputToken?.symbol - const cooldownDays = useMemo(() => { - const seconds = selectedYield.mechanics.cooldownPeriod?.seconds - if (!seconds) return undefined - return Math.ceil(seconds / 86400) - }, [selectedYield.mechanics.cooldownPeriod?.seconds]) + const cooldownDays = useMemo(() => { + const seconds = selectedYield.mechanics.cooldownPeriod?.seconds + if (!seconds) return undefined + return Math.ceil(seconds / 86400) + }, [selectedYield.mechanics.cooldownPeriod?.seconds]) - const symbol = outputSymbol ?? sellAssetSymbol ?? '' + const symbol = outputSymbol ?? sellAssetSymbol ?? '' - const translatedExplainers = useMemo(() => { - if (explainers.length === 0) return [] - return explainers.map(explainer => ({ - icon: explainer.icon, - text: translate(explainer.textKey, { - symbol, - schedule: rewardSchedule ?? '', - days: cooldownDays ?? '', - }), - })) - }, [explainers, translate, symbol, rewardSchedule, cooldownDays]) + const translatedExplainers = useMemo(() => { + if (explainers.length === 0) return [] + return explainers.map(explainer => ({ + icon: explainer.icon, + text: translate(explainer.textKey, { + symbol, + schedule: rewardSchedule ?? '', + days: cooldownDays ?? '', + }), + })) + }, [explainers, translate, symbol, rewardSchedule, cooldownDays]) - if (translatedExplainers.length === 0) return null + if (translatedExplainers.length === 0) return null - return ( - - {translatedExplainers.map((explainer, index) => ( - - {explainer.icon} - - {explainer.text} - - - ))} - - ) -}) + return ( + + {translatedExplainers.map((explainer, index) => ( + + {explainer.icon} + + {explainer.text} + + + ))} + + ) + }, +) diff --git a/src/pages/Yields/components/YieldForm.tsx b/src/pages/Yields/components/YieldForm.tsx index 95ac3a27e51..b40c49021a3 100644 --- a/src/pages/Yields/components/YieldForm.tsx +++ b/src/pages/Yields/components/YieldForm.tsx @@ -10,6 +10,7 @@ import { Icon, Skeleton, Text, + VStack, } from '@chakra-ui/react' import type { AccountId } from '@shapeshiftoss/caip' import { useQueryClient } from '@tanstack/react-query' @@ -506,23 +507,27 @@ export const YieldForm = memo( const statsContent = useMemo( () => ( - - - - {translate('yieldXYZ.currentApy')} - - - {apyDisplay} - - - {hasAmount && ( - + {action === 'enter' && ( + + + {translate('yieldXYZ.currentApy')} + + + {apyDisplay} + + + )} + {action === 'enter' && hasAmount && ( + {translate('yieldXYZ.estYearlyEarnings')} @@ -537,7 +542,7 @@ export const YieldForm = memo( )} {isStaking && maybeSelectedValidatorMetadata && ( - + {translate('yieldXYZ.validator')} @@ -554,7 +559,7 @@ export const YieldForm = memo( )} {(!isStaking || !maybeSelectedValidatorMetadata) && maybeProviderMetadata && ( - + {translate('yieldXYZ.provider')} @@ -571,7 +576,7 @@ export const YieldForm = memo( )} {minDeposit && bnOrZero(minDeposit).gt(0) && action === 'enter' && ( - + {translate(getYieldMinAmountKey(yieldItem.mechanics.type))} @@ -584,7 +589,7 @@ export const YieldForm = memo( )} - + ), [ translate, @@ -748,7 +753,11 @@ export const YieldForm = memo( )} {!isClaimAction && statsContent} {!isClaimAction && ( - + )} {stepsToShow.length > 0 && } From 45836a2812ff942bc1abac71bb84080477e8e219 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Mon, 26 Jan 2026 21:23:47 -1000 Subject: [PATCH 2/2] fix: disable unstake button when no active balance to unstake Co-Authored-By: Claude Opus 4.5 --- src/assets/translations/en/main.json | 1 + .../components/Earn/EarnConfirm.tsx | 6 ++- .../Yields/components/YieldPositionCard.tsx | 45 +++++++++++-------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 09d19a4adbb..65904b2069b 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -2802,6 +2802,7 @@ "depositsDisabledDescription": "Deposits are temporarily unavailable for this yield opportunity.", "withdrawalsDisabled": "Withdrawals Disabled", "withdrawalsDisabledDescription": "Withdrawals are temporarily unavailable for this yield opportunity.", + "noActiveBalanceToExit": "No active balance available to unstake.", "noAvailableYields": "No yield opportunities available for your assets", "connectWalletAvailable": "Connect a wallet to see yields available for your assets", "aboutProvider": "About %{provider}", diff --git a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx index 8af2c6e1f60..93ad42cd741 100644 --- a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx +++ b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx @@ -367,7 +367,11 @@ export const EarnConfirm = memo(() => { {selectedYield && ( - + )} diff --git a/src/pages/Yields/components/YieldPositionCard.tsx b/src/pages/Yields/components/YieldPositionCard.tsx index d928bc3be44..f162cef8eaa 100644 --- a/src/pages/Yields/components/YieldPositionCard.tsx +++ b/src/pages/Yields/components/YieldPositionCard.tsx @@ -12,6 +12,7 @@ import { HStack, Skeleton, Text, + Tooltip, VStack, } from '@chakra-ui/react' import { fromAccountId } from '@shapeshiftoss/caip' @@ -105,6 +106,17 @@ export const YieldPositionCard = memo( const withdrawableBalance = balancesByType?.[YieldBalanceType.Withdrawable] const claimableBalance = balancesByType?.[YieldBalanceType.Claimable] + const hasActiveBalance = Boolean( + activeBalance && bnOrZero(activeBalance.aggregatedAmount).gt(0), + ) + + const isExitDisabled = !yieldItem.status.exit || !hasActiveBalance + + const exitDisabledTitle = useMemo(() => { + if (!yieldItem.status.exit) return translate('yieldXYZ.withdrawalsDisabledDescription') + if (!hasActiveBalance) return translate('yieldXYZ.noActiveBalanceToExit') + }, [hasActiveBalance, translate, yieldItem.status.exit]) + const claimAction = useMemo( () => claimableBalance?.pendingActions?.find(action => @@ -505,24 +517,21 @@ export const YieldPositionCard = memo( {enterLabel} {hasAnyPosition && ( - + + + )}